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

Django Ninja框架外键赋值报错:需传入模型实例而非ID

Django Ninja ForeignKey赋值错误问题分析与解决

问题现象

基于Django 4.1和Ninja 0.19.1开发的项目,通过Swagger或Postman发送POST请求时出现报错:

ValueError: Cannot assign "115": "Offer.currency_to_sell" must be a "Currency" instance.

提交的请求数据:

{
  "currency_to_sell_id": 115,
  "currency_to_buy_id": 116,
  "user_id": 1,
  "amount": 100,
  "exchange_rate": 10
}

相关代码:

api.py接口

@api.post("/add_offer/")
async def add_offer(request, payload: OfferIn):
    offer = await Offer.objects.acreate(**payload.dict())
    return {"id": offer.pk}

schemas.py定义

class OfferIn(ModelSchema):
    class Config:
        model = Offer
        model_fields = [
            "currency_to_sell",
            "currency_to_buy",
            "user",
            "amount",
            "exchange_rate",
        ]

Offer模型

class Offer(models.Model):
    """Sell currency offer model."""

    currency_to_sell = models.ForeignKey(
        to="Currency",
        on_delete=models.CASCADE,
        related_name="currencies_to_sell",
        verbose_name="Currency to sell",
    )
    currency_to_buy = models.ForeignKey(
        to="Currency",
        on_delete=models.CASCADE,
        related_name="currencies_to_buy",
        verbose_name="Currency to buy",
    )
    amount = models.DecimalField(
        decimal_places=2, max_digits=11, blank=False, null=False, verbose_name="Amount"
    )
    exchange_rate = models.DecimalField(
        decimal_places=2,
        max_digits=11,
        blank=False,
        null=False,
        verbose_name="Exchange rate",
    )
    user = models.ForeignKey(
        to=User, on_delete=models.CASCADE, related_name="offers", verbose_name="User"
    )

改用普通Schema而非ModelSchema时可正常运行:

class OfferIn(Schema):
    currency_to_sell_id: int = None
    currency_to_buy_id: int = None
    user_id: int = None
    amount: float
    exchange_rate: float

用户尝试了一种可行方法,但不确定是否正确:

@api.post("/offer/", tags=["Offer"])
async def add_offer(request, payload: OfferIn):

    currency_to_sell = await Currency.objects.aget(id=payload.currency_to_sell)
    currency_to_buy = await Currency.objects.aget(id=payload.currency_to_buy)
    user = await User.objects.aget(id=payload.user)
    payload.currency_to_sell = currency_to_sell
    payload.currency_to_buy = currency_to_buy
    payload.user = user
    offer = await Offer.objects.acreate(**payload.dict())
    return {"id": offer.pk}

错误原因

ModelSchema生成的OfferIn类,默认会将模型中的ForeignKey字段映射为接收对应模型实例的类型,但你提交的是xxx_id格式的纯数字ID。当直接把这些ID通过**payload.dict()传递给acreate时,Django ORM会尝试把数字赋值给需要模型实例的ForeignKey字段,从而触发报错。

而普通Schema中定义的xxx_id字段,Django ORM支持直接通过后缀_id的字段名赋值ForeignKey,因此可以正常运行。


解决方案

方案1:调整请求数据格式(推荐)

Django Ninja的ModelSchema支持直接接收ForeignKey对应的ID值,只需将请求数据中的xxx_id字段名改为模型中定义的字段名(去掉_id后缀):

{
  "currency_to_sell": 115,
  "currency_to_buy": 116,
  "user": 1,
  "amount": 100,
  "exchange_rate": 10
}

此时无需修改任何代码,原有的OfferIn和接口逻辑即可正常工作——Ninja会自动将ID转换为对应的模型实例。

方案2:修改ModelSchema,支持ID字段

如果必须保留xxx_id的请求字段格式,可以在OfferIn中显式定义ID字段,同时保留模型字段的映射:

class OfferIn(ModelSchema):
    currency_to_sell_id: int
    currency_to_buy_id: int
    user_id: int

    class Config:
        model = Offer
        model_fields = [
            "amount",
            "exchange_rate",
        ]

这样提交xxx_id格式的数据时,Schema会正确解析ID值,Django ORM也能通过xxx_id字段直接赋值ForeignKey。

方案3:手动转换实例(优化你的方法)

你手动查询模型实例的方法是可行的,但建议添加异常处理,避免因ID不存在导致的崩溃,同时更清晰地传递参数:

from django.core.exceptions import ObjectDoesNotExist
from ninja import HttpError

@api.post("/offer/", tags=["Offer"])
async def add_offer(request, payload: OfferIn):
    try:
        currency_to_sell = await Currency.objects.aget(id=payload.currency_to_sell)
        currency_to_buy = await Currency.objects.aget(id=payload.currency_to_buy)
        user = await User.objects.aget(id=payload.user)
    except ObjectDoesNotExist as e:
        raise HttpError(404, str(e))
    
    offer = await Offer.objects.acreate(
        currency_to_sell=currency_to_sell,
        currency_to_buy=currency_to_buy,
        user=user,
        amount=payload.amount,
        exchange_rate=payload.exchange_rate
    )
    return {"id": offer.pk}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 04:05:40