如何在Odoo中利用API数据创建联动选择字段?
Odoo 三级联动字段实现方案(外部API驱动)
针对你需要的基于外部API的三级联动字段(Category→Subcategory→Item),同时保存完整API响应的需求,以下是可落地的实现方案,包含模型设计、后端逻辑、视图配置及最佳实践:
1. 模型设计
核心思路是用字符字段存储选中项的唯一标识(比如API返回的ID),搭配JSON字段保存完整API响应,用于后续审计和报表。
from odoo import models, fields, api import requests class ExternalCascadingModel(models.Model): _name = 'external.cascading.model' _description = 'Model with API-driven Cascading Fields' # 类别字段:存ID,JSON存完整响应 category_id = fields.Char(string='Category') category_full_data = fields.Json(string='Category Full Response', readonly=True) # 子类别字段 subcategory_id = fields.Char(string='Subcategory') subcategory_full_data = fields.Json(string='Subcategory Full Response', readonly=True) # 项目字段 item_id = fields.Char(string='Item') item_full_data = fields.Json(string='Item Full Response', readonly=True)
2. 后端联动逻辑
利用Odoo的onchange机制触发API调用,动态更新下一级字段的可选值,同时保存当前选中项的完整响应数据。
2.1 类别切换触发子类别加载
@api.onchange('category_id') def _onchange_category(self): if not self.category_id: # 清空后续字段 self.subcategory_id = False self.subcategory_full_data = False self.item_id = False self.item_full_data = False return {'domain': {'subcategory_id': []}} # 调用API获取子类别列表 try: # 从系统参数读取API密钥,避免硬编码 api_key = self.env['ir.config_parameter'].sudo().get_param('your_module.api_key') headers = {'Authorization': f'Bearer {api_key}'} # 先获取当前类别的完整响应并保存 cat_res = requests.get( f'https://your-api-domain/categories/{self.category_id}', headers=headers, timeout=5 ) cat_res.raise_for_status() self.category_full_data = cat_res.json() # 获取对应子类别选项 subcat_res = requests.get( f'https://your-api-domain/categories/{self.category_id}/subcategories', headers=headers, timeout=5 ) subcat_res.raise_for_status() subcategories = subcat_res.json() # 返回子类别选项给前端 return { 'domain': { 'subcategory_id': [(sub['id'], sub['name']) for sub in subcategories] } } except requests.exceptions.RequestException as e: # 捕获API异常,给用户提示 return { 'warning': { 'title': 'API Error', 'message': f'Failed to load subcategories: {str(e)}' } }
2.2 子类别切换触发项目加载
@api.onchange('subcategory_id') def _onchange_subcategory(self): if not self.subcategory_id or not self.category_id: self.item_id = False self.item_full_data = False return {'domain': {'item_id': []}} try: api_key = self.env['ir.config_parameter'].sudo().get_param('your_module.api_key') headers = {'Authorization': f'Bearer {api_key}'} # 保存子类别完整响应 subcat_res = requests.get( f'https://your-api-domain/subcategories/{self.subcategory_id}', headers=headers, timeout=5 ) subcat_res.raise_for_status() self.subcategory_full_data = subcat_res.json() # 获取项目选项 item_res = requests.get( f'https://your-api-domain/subcategories/{self.subcategory_id}/items', headers=headers, timeout=5 ) item_res.raise_for_status() items = item_res.json() return { 'domain': { 'item_id': [(item['id'], item['name']) for item in items] } } except requests.exceptions.RequestException as e: return { 'warning': { 'title': 'API Error', 'message': f'Failed to load items: {str(e)}' } }
2.3 项目选中后保存完整响应
@api.onchange('item_id') def _onchange_item(self): if not self.item_id: self.item_full_data = False return try: api_key = self.env['ir.config_parameter'].sudo().get_param('your_module.api_key') headers = {'Authorization': f'Bearer {api_key}'} item_res = requests.get( f'https://your-api-domain/items/{self.item_id}', headers=headers, timeout=5 ) item_res.raise_for_status() self.item_full_data = item_res.json() except requests.exceptions.RequestException as e: return { 'warning': { 'title': 'API Error', 'message': f'Failed to load item details: {str(e)}' } }
3. 前端视图配置
在form视图中使用selection widget,让字符字段支持下拉选择,并通过onchange动态更新选项:
<record id="external_cascading_model_form" model="ir.ui.view"> <field name="name">external.cascading.model.form</field> <field name="model">external.cascading.model</field> <field name="arch" type="xml"> <form string="API Cascading Fields"> <sheet> <group> <field name="category_id" widget="selection" options="{'no_create': True}" required="1"/> <field name="subcategory_id" widget="selection" options="{'no_create': True}" required="1"/> <field name="item_id" widget="selection" options="{'no_create': True}" required="1"/> <!-- 审计用完整数据字段,可设为隐藏或只读 --> <group string="Audit Data" colspan="2"> <field name="category_full_data" readonly="1" style="width: 100%"/> <field name="subcategory_full_data" readonly="1" style="width: 100%"/> <field name="item_full_data" readonly="1" style="width: 100%"/> </group> </group> </sheet> </form> </field> </record>
4. 最佳实践
- API缓存:对重复的API请求添加缓存(比如用
@api.model配合functools.lru_cache),减少外部调用次数:from functools import lru_cache @api.model @lru_cache(maxsize=128) def _get_categories(self): # 实现类别列表缓存 pass - 异步处理:如果API响应慢,可结合
queue_job模块将非实时的API调用转为异步任务,但联动场景建议同步调用(加超时控制)。 - 数据验证:保存JSON数据前,验证响应格式是否符合业务需求,避免无效数据入库。
- 权限安全:API密钥等敏感信息必须存在
ir.config_parameter中,绝不能硬编码在代码里。 - 异常处理:所有API调用都要捕获异常,给用户明确的错误提示,避免系统崩溃。
内容的提问来源于stack exchange,提问作者Ammar Ahmed Khan
相关产品推荐
相关产品推荐

