You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 05:40:07