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

Django REST Framework创建关联字段记录时遇非空约束错误求助

问题:创建关联已有Company的BalanceSheet实例时触发IntegrityError

这是个新手问题,我在StackOverflow没找到足够匹配的答案,所以来提问。我在创建含外键关联已有记录的模型实例时遇到困难,不清楚代码问题出在哪。

我的应用里有Company和BalanceSheet两个模型,是一对多关系:

模型代码

class Company(models.Model):
    cik = models.IntegerField(default=0, unique=True)
    symbol = models.CharField(max_length=4, unique=True)
    name = models.CharField(max_length=255, unique=True)
    def __str__(self):
        return self.symbol

class BalanceSheet(models.Model):
    company = models.ForeignKey(Company, null=True, on_delete=models.CASCADE, related_name='balance_sheets',)
    date = models.DateField()
    profit = models.BigIntegerField()
    loss = models.BigIntegerField()
    class Meta:
        unique_together = (('company', 'date'),)
    def __str__(self):
        return '%s - %s' % (self.company, self.date)

序列化器代码

class BalanceSheetSerializer(serializers.ModelSerializer):
    company = serializers.StringRelatedField()
    class Meta:
        model = BalanceSheet
        fields = ('company','date','profit','loss')

class CompanySerializer(serializers.ModelSerializer):
    class Meta:
        model = Company
        fields = ('cik', 'symbol', 'name')

视图代码

class BalanceSheetCreate(generics.CreateAPIView):
    model = BalanceSheet
    queryset = BalanceSheet.objects.all()
    serializer_class = BalanceSheetSerializer

URL配置

url(r'^(?P<symbol>[A-Z]{1,4})/create-balance-sheet/$', views.BalanceSheetCreate.as_view(), name='create_balance_sheet'),

目前读取数据没问题,但创建记录时出错:
执行curl命令:

curl http://localhost:8000/financials/AAPL/create-balance-sheet/ -X POST -d "company=AAPL&date=1968-04-17&profit=1&loss=1"

报错信息:

IntegrityError at /financials/AAPL/create-balance-sheet/ null value in column "company_id" violates not-null constraint

即使去掉POST数据里的company参数,还是报相同错误。我以为已经通过URL和POST数据指定了关联公司,请问该怎么解决这个错误?

环境:Python3.6、Django1.11、Django REST Framework3.7.7


解决方案

我来帮你分析下问题所在,核心问题出在你的BalanceSheetSerializer里用的StringRelatedField上——这个字段是只读的!它只会把外键对应的__str__值返回给前端,完全不处理前端传过来的用于创建关联的数据,所以DRF根本没把你传的company=AAPL转换成对应的Company实例,最终导致company_id为空,触发了数据库的非空约束错误。

下面给你两种可行的解决办法,你可以根据自己的需求选择:

方法一:修改序列化器,支持通过symbol关联Company

把StringRelatedField换成SlugRelatedField,这个字段可以通过指定的slug字段(这里就是symbol)来关联已有的Company实例,同时支持读写操作。修改后的序列化器代码如下:

class BalanceSheetSerializer(serializers.ModelSerializer):
    company = serializers.SlugRelatedField(
        queryset=Company.objects.all(),
        slug_field='symbol'  # 指定用Company的symbol字段来匹配
    )
    class Meta:
        model = BalanceSheet
        fields = ('company','date','profit','loss')

修改后,你之前的curl命令就能正常工作了,DRF会自动根据你传的company=AAPL找到对应的Company实例,然后关联到新创建的BalanceSheet上。

方法二:从URL参数自动获取Company,无需前端传company参数

既然你的URL里已经包含了symbol参数(比如AAPL),其实可以直接从URL里拿到这个值,然后在视图里自动设置关联的Company,这样前端请求时就不需要再传company参数了,既简化了请求,也避免了前端传错的可能。

修改你的视图代码,重写perform_create方法:

class BalanceSheetCreate(generics.CreateAPIView):
    model = BalanceSheet
    queryset = BalanceSheet.objects.all()
    serializer_class = BalanceSheetSerializer

    def perform_create(self, serializer):
        # 从URL参数里获取symbol,找到对应的Company实例
        symbol = self.kwargs.get('symbol')
        company = Company.objects.get(symbol=symbol)
        # 保存时传入company参数
        serializer.save(company=company)

同时,你需要把序列化器里的company字段改回默认的外键处理(或者设置为只读,因为不需要前端传了):

class BalanceSheetSerializer(serializers.ModelSerializer):
    # 如果需要返回company的信息,可以保留StringRelatedField,但要加上read_only=True
    company = serializers.StringRelatedField(read_only=True)
    class Meta:
        model = BalanceSheet
        fields = ('company','date','profit','loss')

这时候你的curl命令就可以去掉company=AAPL参数了:

curl http://localhost:8000/financials/AAPL/create-balance-sheet/ -X POST -d "date=1968-04-17&profit=1&loss=1"

小提示

第二种方法其实更推荐,因为URL已经明确指定了要关联的公司,前端不需要重复传递,而且能保证创建的BalanceSheet一定属于URL里指定的Company,避免出现前后端传递不一致的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:43:53