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()
尝试批量更新时遇到两个错误:
使用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."]}使用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'
错误原因分析
- Schematics验证拦截F表达式:Item模型初始化时会立即校验字段类型,F表达式是Django的查询构造对象,并非Decimal类型,因此触发验证失败。
- 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
相关产品推荐
相关产品推荐

