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

Django REST Framework多对多字段前端传值报错解决方案咨询

Django REST Framework多对多关联POST数据处理问题解决

问题场景

实现Post与PostCategory的多对多关联时,前端通过POST传递包含逗号分隔分类名称的FormData,当前及预判会遇到两个问题:

  1. 即时报错:post_categories [ "Incorrect type. Expected pk value, received str." ]
  2. 修复后预判报错:Direct assignment to the forward side of a many-to-many set is prohibited. Use post_categories.set() instead.

相关代码

models.py

class PostCategory(models.Model):
    name = models.CharField(max_length=200, unique=True)
    user = models.ForeignKey(User, on_delete=models.CASCADE)

    def __str__(self):
        return self.name

class Post(models.Model):
    title = models.CharField(max_length=255)
    user = models.ForeignKey(User, on_delete=models.DO_NOTHING)
    content = models.TextField()
    post_categories = models.ManyToManyField(PostCategory, blank=True)

    def __str__(self):
        return self.title

serializers.py(原代码)

class PostSerializer(serializers.ModelSerializer):
    user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())

    class Meta:
        model = Post
        exclude = []
        
    def create(self, validated_data):
        user = self.context['request'].user
        validated_data['user'] = user
        post = Post.objects.create(**validated_data)
        return post

views.py

class PostCreateView(APIView):
    def post(self, request):
        if request.user.is_authenticated:
            serializer = PostSerializer(data = request.data, context={'request': request})
            if serializer.is_valid():
                serializer.save()
                return Response(serializer.data, status=status.HTTP_201_CREATED)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

前端POST数据

FormData(3) { title → "Title 1", content → "Content 1", post_categories → "web framework,javascript"}

解决方法

1. 处理字符串格式的分类数据(解决类型错误)

自定义序列化字段,将前端传递的逗号分隔字符串转换为对应的PostCategory实例集合:

from rest_framework import serializers

class CategoryListField(serializers.Field):
    def to_representation(self, value):
        # 序列化时返回逗号分隔的分类名称
        return ','.join([category.name for category in value.all()])
    
    def to_internal_value(self, data):
        # 反序列化时解析字符串,查找或创建分类(根据业务需求调整逻辑)
        user = self.context['request'].user
        category_names = [name.strip() for name in data.split(',') if name.strip()]
        categories = []
        for name in category_names:
            # 利用唯一约束,避免重复创建分类
            category, _ = PostCategory.objects.get_or_create(name=name, user=user)
            categories.append(category)
        return categories

在PostSerializer中替换默认的post_categories字段:

class PostSerializer(serializers.ModelSerializer):
    user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())
    post_categories = CategoryListField()  # 使用自定义字段

    class Meta:
        model = Post
        exclude = []

2. 正确处理多对多关联赋值(解决禁止直接赋值问题)

修改serializer的create方法,先创建Post实例,再通过set()方法设置多对多关系:

def create(self, validated_data):
    user = self.context['request'].user
    # 先取出分类数据,多对多字段不能直接传入create方法
    categories = validated_data.pop('post_categories', [])
    # 创建Post实例
    post = Post.objects.create(user=user, **validated_data)
    # 设置多对多关联
    post.post_categories.set(categories)
    return post

完整修改后的serializers.py

from rest_framework import serializers
from .models import Post, PostCategory

class CategoryListField(serializers.Field):
    def to_representation(self, value):
        return ','.join([category.name for category in value.all()])
    
    def to_internal_value(self, data):
        user = self.context['request'].user
        category_names = [name.strip() for name in data.split(',') if name.strip()]
        categories = []
        for name in category_names:
            category, _ = PostCategory.objects.get_or_create(name=name, user=user)
            categories.append(category)
        return categories

class PostSerializer(serializers.ModelSerializer):
    user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())
    post_categories = CategoryListField()

    class Meta:
        model = Post
        exclude = []
        
    def create(self, validated_data):
        user = self.context['request'].user
        categories = validated_data.pop('post_categories', [])
        post = Post.objects.create(user=user, **validated_data)
        post.post_categories.set(categories)
        return post

修改完成后,前端传递的分类字符串会被正确解析为PostCategory实例,多对多关联也会通过合规方式设置,两个问题均可解决。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 14:32:28