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

Django ORM批量更新嵌套JSON字段的F表达式及字段不存在问题

Django嵌套JSON字段批量更新问题解决

问题背景

现有如下Django与Schematics模型:

from django.db import models
from schematics import models as schema_models

class Recipt(models.Model):
    id = UUID()
    is_red = models.BooleanField(default=False)
    disputed_at = models.DateTimeField(null=True)
    total_amount = models.DecimalField(max_digits=12, decimal_places=2)
    details = JSONModelField(ReciptDetails)

class ReciptDetails(PickleableModel):
    id = UUID()
    item = schema_types.ModelType(Item)

class Item(schema_models.Model):
    id = schema_types.UUIDType()
    name = schema_types.StringType()
    amount = schema_types.DecimalType()
    description = schema_types.StringType()

尝试批量更新时遇到两个错误:

  1. 使用F表达式关联total_amount到item.amount时,触发Schematics验证错误:

    Recipt.objects.filter(Q(details__item=None), is_red=True).update(details__item=Item({"name": SOMECONST.HAPPY, "amount": F('total_amount')}))
    

    错误:schematics.exceptions.DataError: {"amount": ["Value 'F(total_amount)' is not decimal."]}

  2. 使用Decimal常量时,触发Django字段不存在错误:

    Recipt.objects.filter(Q(details__item=None), is_red=True).update(details__item=Item({"name": SOMECONST.HAPPY, "amount": Decimal("10.00")}))
    

    错误:django.core.exceptions.FieldDoesNotExist: Recipt has no field named 'details__item'

错误原因分析

  1. Schematics验证拦截F表达式:Item模型初始化时会立即校验字段类型,F表达式是Django的查询构造对象,并非Decimal类型,因此触发验证失败。
  2. Django ORM不支持自定义JSON字段的嵌套路径更新:details是自定义的JSONModelField,Django无法识别details__item这种嵌套字段路径,直接使用会被判定为不存在的字段。

解决方案

方案1:使用数据库原生JSON函数(推荐,批量高效)

以PostgreSQL为例(支持jsonb_set函数),直接在数据库层面更新嵌套JSON属性,绕过Schematics的即时验证(数据读取时Schematics会自动解析):

from django.db.models import F, Func, Value
from django.db.models.functions import JSONObject
import uuid

# 构造要插入的Item JSON结构,用F表达式关联total_amount
item_json = JSONObject(
    name=Value(SOMECONST.HAPPY),
    amount=F('total_amount'),
    id=Value(uuid.uuid4())  # 按需生成Item的UUID
)

# 调用jsonb_set更新details字段的item属性
Recipt.objects.filter(
    Q(details__item=None), 
    is_red=True
).update(
    details=Func(
        F('details'),
        Value('{item}'),  # JSON路径,指向details中的item节点
        item_json,
        function='jsonb_set'
    )
)

方案2:兼容Django原生JSONField的嵌套更新(若适用)

如果你的JSONModelField是Django原生JSONField的子类,且使用Django 3.1+版本,可直接用嵌套路径+JSONObject更新:

from django.db.models import F, Value
from django.db.models.functions import JSONObject
import uuid

Recipt.objects.filter(
    Q(details__item=None), 
    is_red=True
).update(
    details__item=JSONObject(
        name=Value(SOMECONST.HAPPY),
        amount=F('total_amount'),
        id=Value(uuid.uuid4())
    )
)

方案3:批量获取后逐个更新(不推荐,效率低)

若数据库不支持JSON函数,可批量获取对象后逐个更新并保存,适合数据量较小的场景:

import uuid

recipts = Recipt.objects.filter(Q(details__item=None), is_red=True)
for recipt in recipts:
    recipt.details.item = Item({
        "name": SOMECONST.HAPPY,
        "amount": recipt.total_amount,
        "id": uuid.uuid4()
    })
    recipt.save()

内容的提问来源于stack exchange,提问作者Fredy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 20:52:05