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

Django ManyToManyField插入异常:外键约束失败及标签字段为空

Django DRF多对多关联插入数据时外键约束错误及标签为空问题解决

问题描述

在Django项目中,Assignment模型与Tag模型通过ManyToManyField关联,使用Django REST Framework序列化器插入数据时出现两个问题:

  • 标签字段返回空列表[]
  • 触发django.db.utils.IntegrityError: FOREIGN KEY constraint failed错误

相关代码及请求数据

models.py

from django.db import models


class Tag(models.Model):
    """Tag for filtering Assignments."""

    name = models.CharField(max_length=255, unique=True)

    def __str__(self):
        return self.name


class Assignment(models.Model):
    """Models for assigment"""

    name = models.CharField(max_length=255)
    tags = models.ManyToManyField(Tag, blank=True)
    assignment_data = models.JSONField(blank=True)
    last_updated_by = models.CharField(max_length=255, blank=True)

    def __str__(self):
        return self.name

serializers.py

from rest_framework import serializers

from assignment.models import (
    Assignment,
    Tag,
)
class TagSerializer(serializers.ModelSerializer):
    """Serializer for tags."""

    class Meta:
        model = Tag
        fields = ["name"]


class AssignmentSerializer(serializers.ModelSerializer):
    """Serializer for Assignment."""
    tags = TagSerializer(many=True, required=False)


    class Meta:
        model = Assignment
        fields = ["id", "name", "tags", "last_updated_by", "assignment_data"]
        read_only_fields = ["id"]

    def create(self, validated_data):
        """Create an Assignment."""
        tags_data = validated_data.pop("tags", [])
        instance = Assignment.objects.create(**validated_data)

        for tag_name in tags_data:
            created = Tag.objects.get_or_create(name=tag_name)
            instance.tags.set(tags)
        instance.save()

        return instance

请求数据

{
    "name": "demo assignment",
    "tags": ["demo"],
    "last_updated_by": "user1",
    "assignment_data":  {
                "hello": "Hello world 2"
            }
}

错误信息

File "D:\Assignments-Api\.venv\Lib\site-packages\django\db\backends\base\base.py", line 313, in _commit
    return self.connection.commit()
           ^^^^^^^^^^^^^^^^^^^^^^^^
django.db.utils.IntegrityError: FOREIGN KEY constraint failed

错误分析

  1. 嵌套序列化器导致数据格式不匹配:因为AssignmentSerializer中tags字段使用了TagSerializer嵌套,validated_data里的tags实际是字典列表(如[{"name": "demo"}]),而非请求中的字符串列表["demo"],直接将字典传给Tag.objects.get_or_create(name=tag_name)会导致类型错误,进而触发外键约束异常。
  2. create方法逻辑错误:
    • Tag.objects.get_or_create()返回的是元组(tag实例, 是否创建),原代码未正确提取Tag实例
    • 调用instance.tags.set(tags)时,tags变量未定义
    • 在循环内多次调用set()方法不合理,应该收集所有Tag实例后一次性关联

修复方案

修改AssignmentSerializer的create方法,正确处理嵌套序列化的tags数据,并修复关联逻辑:

修复后的serializers.py

from rest_framework import serializers

from assignment.models import (
    Assignment,
    Tag,
)
class TagSerializer(serializers.ModelSerializer):
    """Serializer for tags."""

    class Meta:
        model = Tag
        fields = ["name"]


class AssignmentSerializer(serializers.ModelSerializer):
    """Serializer for Assignment."""
    tags = TagSerializer(many=True, required=False)

    class Meta:
        model = Assignment
        fields = ["id", "name", "tags", "last_updated_by", "assignment_data"]
        read_only_fields = ["id"]

    def create(self, validated_data):
        """Create an Assignment."""
        tags_data = validated_data.pop("tags", [])
        instance = Assignment.objects.create(**validated_data)
        
        # 收集所有要关联的Tag实例
        tag_instances = []
        for tag_item in tags_data:
            # 从嵌套字典中取出name值
            tag_name = tag_item.get("name")
            if tag_name:
                # 提取get_or_create返回的Tag实例
                tag_instance, _ = Tag.objects.get_or_create(name=tag_name)
                tag_instances.append(tag_instance)
        
        # 一次性关联所有Tag实例
        if tag_instances:
            instance.tags.set(tag_instances)
        
        return instance

简化方案(无需嵌套返回标签详情)

如果不需要返回标签的嵌套详情,仅通过字符串列表传递标签名称,可以改用StringRelatedField,代码更简洁:

class AssignmentSerializer(serializers.ModelSerializer):
    tags = serializers.StringRelatedField(many=True, required=False)

    class Meta:
        model = Assignment
        fields = ["id", "name", "tags", "last_updated_by", "assignment_data"]
        read_only_fields = ["id"]

    def create(self, validated_data):
        tags_data = validated_data.pop("tags", [])
        instance = Assignment.objects.create(**validated_data)
        
        tag_instances = []
        for tag_name in tags_data:
            tag_instance, _ = Tag.objects.get_or_create(name=tag_name)
            tag_instances.append(tag_instance)
        
        instance.tags.set(tag_instances)
        return instance

此方案下请求数据保持原格式即可,StringRelatedField会自动处理字符串与Tag名称的映射。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 23:27:35