深入解析 Odoo 中 default_get 方法的功能

什么是 default_get

想象一下你走进一家智能咖啡店

  • 店员(Odoo系统)知道你昨天点了拿铁
  • 今天你刚进门,店员就问:"还是老样子,一杯中杯拿铁吗?"
  • 你只需说"是"或进行少量调整

default_get 就是这个智能店员 ------它在你创建新记录时,自动提供合理的默认值,让你只需关注需要修改的部分。

default_get 的核心功能

1. 智能预填充字段值

default_get 的主要工作是:在用户创建新记录时,自动填充合理的初始值,而不是让所有字段都为空。

复制代码
@api.model
def default_get(self, fields):
    # ...处理逻辑...
    return defaults  # 返回包含默认值的字典

2. 与普通默认值的区别

|------------------------|----------|---------|-----------|
| 默认值类型 | 设置位置 | 灵活性 | 依赖上下文 |
| 字段定义中的 default | 模型字段定义 | 低 | ❌ 不依赖 |
| _defaults****属性 | 模型类属性 | 中 | ❌ 不依赖 |
| default_get****方法 | 模型方法 | 高 | ✅ 依赖 |

  • 字段 default:只能设置简单值或无参函数

    name = fields.Char(default="New Meeting")

  • default_get:可以基于完整上下文提供智能默认值

    如果从联系人页面创建,自动将该联系人设为参会者

    if context.get('active_model') == 'res.partner':
    defaults['partner_ids'] = [(4, context.get('active_id'))]

在日历事件中的实际应用

查看提供的代码:

复制代码
@api.model
def default_get(self, fields):
    # 处理 res_model 和 res_model_id 的相互转换
    context = dict(self.env.context)
    if context.get('default_res_model') and not context.get('default_res_model_id'):
        context.update(
            default_res_model_id=self.env['ir.model']._get_id(context['default_res_model'])
        )
    if context.get('default_res_model_id') and not context.get('default_res_model'):
        context.update(
            default_res_model=self.env['ir.model'].browse(self.env.context['default_res_model_id']).sudo().model
        )

    # 调用父类的 default_get 获取基础默认值
    defaults = super(CalendarEvent, self.with_context(context)).default_get(fields)

    # 处理从其他模型创建日历事件的场景
    if 'res_model_id' not in defaults and 'res_model_id' in fields and \
            context.get('active_model') and context['active_model'] != 'calendar.event':
        defaults['res_model_id'] = self.env['ir.model']._get_id(context['active_model'])
        defaults['res_model'] = context.get('active_model')
    if 'res_id' not in defaults and 'res_id' in fields and \
            defaults.get('res_model_id') and context.get('active_id'):
        defaults['res_id'] = context['active_id']

    return defaults

通俗解释这段代码

想象你正在从客户联系人页面创建会议

  1. 上下文准备阶段
    • 你点击联系人 John Doe 页面上的"安排会议"按钮
    • 系统自动设置上下文:{'active_model': 'res.partner', 'active_id': john_doe_id}
  2. default_get****工作流程
    • 检查是否有模型名称但没有模型ID → 自动转换
    • 检查是否有模型ID但没有模型名称 → 自动转换
    • 调用父类获取基础默认值(如当前用户作为组织者)
    • 发现是从联系人页面创建 → 自动关联到 John Doe
    • 返回包含这些智能默认值的字典
  3. 结果
    • 会议表单自动打开
    • "组织者"已设为你自己
    • "参会者"已包含 John Doe
    • 无需手动选择联系人

default_get 的工作原理

1. 调用时机

default_get 在以下情况被调用:

  • ✅ 用户点击"创建"按钮时
  • ✅ 从其他记录打开"创建"向导时(如从销售订单创建发票)
  • ✅ 通过代码调用 create() 但未提供某些字段值时
  • ✅ 通过API创建新记录时

2. 方法签名

复制代码
@api.model
def default_get(self, fields):
    # self: 空记录集(因为是创建新记录)
    # fields: 需要获取默认值的字段列表
    # 返回: 包含字段名和默认值的字典

3. 完整工作流程

  1. 接收字段列表:系统告诉方法"我需要这些字段的默认值"
  2. 检查上下文:查看是否有特殊参数影响默认值
  3. 计算默认值:基于上下文和业务逻辑计算
  4. 返回结果:提供字段名→默认值的映射

日历模块中的智能默认值示例

示例 1:自动关联源记录

复制代码
# 当从销售订单创建会议时
if context.get('active_model') == 'sale.order':
    defaults['res_model'] = 'sale.order'
    defaults['res_model_id'] = self.env['ir.model']._get_id('sale.order')
    defaults['res_id'] = context.get('active_id')

效果:会议自动关联到当前销售订单,无需手动选择。

示例 2:自动设置参会者

复制代码
@api.model
def _default_partners(self):
    """当从联系人页面创建时,自动添加该联系人为参会者"""
    partners = self.env.user.partner_id
    active_id = self.env.context.get('active_id')
    if self.env.context.get('active_model') == 'res.partner' and active_id:
        partners |= self.env['res.partner'].browse(active_id)
    return partners

@api.model
def default_get(self, fields):
    defaults = super().default_get(fields)
    if 'partner_ids' not in defaults and 'partner_ids' in fields:
        defaults['partner_ids'] = [(6, 0, self._default_partners().ids)]
    return defaults

效果:从联系人页面点击"安排会议",该联系人自动成为参会者。

示例 3:智能时间对齐

复制代码
@api.model
def _default_start(self):
    now = fields.Datetime.now()
    return now + (datetime.min - now) % timedelta(minutes=30)

@api.model
def default_get(self, fields):
    defaults = super().default_get(fields)
    if 'start' not in defaults and 'start' in fields:
        defaults['start'] = self._default_start()
    if 'stop' not in defaults and 'stop' in fields:
        defaults['stop'] = self._default_stop()
    return defaults

效果:开始时间自动对齐到最近的30分钟间隔(如10:17 → 10:30)。

为什么需要 default_get?(业务价值)

1. 提升用户体验

  • 减少点击次数:自动填充已知信息
  • 降低错误率:减少手动输入
  • 上下文感知:根据来源提供相关默认值

2. 支持复杂业务流程

  • 跨模型关联:从销售订单创建发票时自动关联
  • 动态默认值:基于用户、时间、位置等动态计算
  • 向导集成:在多步向导中传递上下文

3. 实现"智能助手"功能

  • 预测用户意图:从联系人页面创建 → 自动添加该联系人
  • 提供合理建议:会议默认时长1小时
  • 简化复杂操作:重复事件自动设置合理默认值

实战:自定义 default_get

场景:销售团队希望会议默认包含销售经理

复制代码
@api.model
def default_get(self, fields):
    # 先获取父类的默认值
    defaults = super(CalendarEvent, self).default_get(fields)
  
    # 获取销售经理
    sales_manager = self.env.user.company_id.sales_manager_id
  
    # 如果有销售经理且是销售团队成员创建会议
    if sales_manager and self.env.user.has_group('sales_team.group_sale_salesman'):
        # 获取当前参会者
        current_partners = defaults.get('partner_ids', [])
        # 确保是有效的命令格式
        if not current_partners or not isinstance(current_partners[0], (list, tuple)):
            current_partners = [(6, 0, current_partners)]
        # 添加销售经理
        partner_ids = current_partners[0][2] if current_partners else []
        partner_ids.append(sales_manager.partner_id.id)
        defaults['partner_ids'] = [(6, 0, list(set(partner_ids)))]
  
    return defaults

效果:销售团队成员创建会议时,销售经理自动成为参会者。

高级技巧:基于时间的智能默认值

复制代码
@api.model
def default_get(self, fields):
    defaults = super().default_get(fields)
  
    # 如果是下午3点后创建会议,默认安排在明天
    now = datetime.now()
    if now.hour >= 15:
        tomorrow = now + timedelta(days=1)
        # 设置为明天上午10点
        defaults['start'] = tomorrow.replace(hour=10, minute=0, second=0, microsecond=0)
        defaults['stop'] = defaults['start'] + timedelta(hours=1)
  
    return defaults

常见问题与解决方案

问题 1:默认值不生效

原因

  • 字段未包含在 fields 参数中
  • 父类 default_get 已经设置了该字段
  • 表单视图中硬编码了默认值

解决方案

复制代码
@api.model
def default_get(self, fields):
    defaults = super().default_get(fields)
    # 确保只设置请求的字段
    for field in ['name', 'description']:
        if field in fields and field not in defaults:
            defaults[field] = "Custom default"
    return defaults

问题 2:上下文参数未正确传递

原因

  • 从JavaScript调用时未正确设置上下文
  • 向导中丢失了原始上下文

解决方案

复制代码
# 在JavaScript中
this._rpc({
    model: 'calendar.event',
    method: 'create',
    args: [{}],
    context: Object.assign({}, this.context, {
        default_res_model: 'sale.order',
        default_res_id: orderId
    })
});

最佳实践

1. 遵循"最少假设"原则

复制代码
# 好的做法:只设置明确请求的字段
if 'start' in fields and 'start' not in defaults:
    defaults['start'] = self._calculate_start_time()

# 避免:设置所有可能的字段
# defaults.setdefault('start', ...)
# defaults.setdefault('stop', ...)

2. 保留父类逻辑

复制代码
# 总是先调用父类
defaults = super().default_get(fields)

# 然后添加/修改需要的默认值
if 'partner_ids' in fields and not defaults.get('partner_ids'):
    defaults['partner_ids'] = self._get_default_partners()

3. 处理多种上下文场景

复制代码
@api.model
def default_get(self, fields):
    defaults = super().default_get(fields)
    ctx = self.env.context
  
    # 从联系人创建
    if ctx.get('active_model') == 'res.partner':
        defaults = self._handle_partner_context(defaults, ctx)
  
    # 从销售订单创建
    elif ctx.get('active_model') == 'sale.order':
        defaults = self._handle_sale_order_context(defaults, ctx)
  
    # 普通创建
    else:
        defaults = self._handle_normal_creation(defaults)
  
    return defaults

4. 提供可扩展的钩子

复制代码
@api.model
def default_get(self, fields):
    defaults = super().default_get(fields)
  
    # 允许子类扩展
    self._update_default_values(defaults, fields)
  
    return defaults

def _update_default_values(self, defaults, fields):
    """钩子方法,供继承类扩展默认值逻辑"""
    pass

总结:default_get 的核心价值

default_get 是 Odoo 中用户体验的关键,它:

  1. 变"填空题"为"选择题"

不再让用户从零开始,而是提供合理的起点

  1. 理解用户意图

从哪里来(上下文)→ 想要什么(智能默认值)

  1. 减少重复劳动

自动填充已知信息,让用户专注关键内容

  1. 实现业务规则

通过默认值实施公司政策(如会议必须包含经理)

💡 关键认知default_get 不是简单的"设置默认值",而是 "理解用户上下文并提供智能建议" 的过程。好的 default_get 实现能让用户感觉系统"懂我",大幅提升使用体验。

在日历模块中,default_get 确保了无论用户从哪里创建会议(联系人页面、销售订单、直接创建),都能获得最相关的默认设置,使日程安排变得简单高效。

相关推荐
团子的二进制世界1 小时前
Sentinel-服务保护(限流、熔断降级)
java·开发语言·sentinel·异常处理
NWU_白杨2 小时前
多线程安全与通信问题
java
阿猿收手吧!2 小时前
【C++】模板偏特化与std::move深度解析
服务器·c++
sheji34162 小时前
【开题答辩全过程】以 工业车辆维修APP设计与实现为例,包含答辩的问题和答案
java
虫小宝2 小时前
淘客系统的容灾演练与恢复:Java Chaos Monkey模拟节点故障下的服务降级与快速切换实践
java·开发语言
yxm26336690812 小时前
【洛谷压缩技术续集题解】
java·开发语言·算法
键盘帽子2 小时前
多线程情况下长连接中的session并发问题
java·开发语言·spring boot·spring·spring cloud
无名-CODING2 小时前
Spring事务管理完全指南:从零到精通(上)
java·数据库·spring
fengxin_rou2 小时前
【黑马点评实战篇|第一篇:基于Redis实现登录】
java·开发语言·数据库·redis·缓存