Django REST Framework多对多字段前端传值报错解决方案咨询
Django REST Framework多对多关联POST数据处理问题解决
问题场景
实现Post与PostCategory的多对多关联时,前端通过POST传递包含逗号分隔分类名称的FormData,当前及预判会遇到两个问题:
- 即时报错:
post_categories [ "Incorrect type. Expected pk value, received str." ] - 修复后预判报错:
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
相关产品推荐
相关产品推荐

