Django REST Framework创建文档自动关联当前用户及报错排查
问题根因
你报400和传不传user字段没有关系,核心问题有三个:
- 你已经在序列化器里把user设为
read_only=True+CurrentUserDefault(),后端会自动从登录态里取当前请求的用户赋值,前端根本不需要传这个字段,传了也会被序列化器直接忽略。 - 序列化器校验不通过:你声明了
mysource = MySourceSerializer(many=True)但没给它加非必填配置,前端没传这个字段时会直接触发校验错误;同时你前端传的字段叫templateId,但模型里对应字段名是template,字段名不匹配也会校验失败。 - 序列化器create方法逻辑有漏洞:你只pop了question_blocks、outline_blocks两个嵌套关联字段,漏了mysource,就算校验通过,创建Document对象时也会因为传入了模型不存在的字段报错。
具体修改步骤
1. 后端修改(Django REST Framework)
首先改serializers.py,给嵌套关联字段加非必填配置,补全create方法的字段处理:
class DocumentSerializer(serializers.ModelSerializer): id = HashidSerializerCharField(source_field="documents.Document.id", read_only=True) # 所有嵌套关联字段都加required=False、默认空列表,避免不传就报400 question_blocks = QuestionBlockSerializer(many=True, required=False, default=list) outline_blocks = OutlineBlockSerializer(many=True, required=False, default=list) mysource = MySourceSerializer(many=True, required=False, default=list) # user字段配置正确,无需修改,会自动取当前登录用户 user = serializers.PrimaryKeyRelatedField( read_only=True, default=serializers.CurrentUserDefault() ) class Meta: model = Document fields = "__all__" def create(self, validated_data): # 所有嵌套关联字段都要先pop出来,否则传给Document.create会报未知字段错误 question_blocks = validated_data.pop("question_blocks") outline_blocks = validated_data.pop("outline_blocks") mysource = validated_data.pop("mysource") # 补上之前漏的这行 document = Document.objects.create(**validated_data) # 关联数据创建逻辑和之前一致,如果不需要mysource字段,直接删掉序列化器里的mysource声明即可 for qBlock in question_blocks: QuestionBlock.objects.create(document=document, **qBlock) for oBlock in outline_blocks: OutlineBlock.objects.create(document=document, **oBlock) document.save() return document
然后修改文档对应的视图(ViewSet/APIView),实现「用户只能查询自己的文档」需求,同时保证创建时自动绑定用户:
- 给视图加权限类
permission_classes = [IsAuthenticated],确保只有登录用户能访问接口,否则CurrentUserDefault拿不到有效用户; - 重写get_queryset方法,过滤出当前用户所属的文档:
from rest_framework import viewsets from rest_framework.permissions import IsAuthenticated class DocumentViewSet(viewsets.ModelViewSet): serializer_class = DocumentSerializer permission_classes = [IsAuthenticated] def get_queryset(self): # 只返回当前登录用户自己创建的文档 return Document.objects.filter(user=self.request.user)
注意:如果用SessionAuthentication做登录态校验,要确保Django的CORS配置开了CORS_ALLOW_CREDENTIALS = True,同时允许的源不能设为*,必须明确写前端的域名,否则withCredentials的请求会被跨域拦截。
2. 前端修改(React)
直接删掉data里的user字段即可,完全不需要传值,同时把字段名templateId改成template和后端字段对齐:
export const createDocTemp = ({ router, title, templateId, question_blocks, outline_blocks, }: NewDocument) => { const data = { // 删掉user相关代码,不需要前端传 title, template: templateId, // 字段名改为template,和后端模型对应 question_blocks: question_blocks || [], // 空值时传空数组,避免校验报错 outline_blocks: outline_blocks || [], }; axios({ method: 'post', url: `${process.env.NEXT_PUBLIC_API_URL}/api/v1/documents/`, headers: { 'Content-Type': 'application/json' }, data, withCredentials: true, }) .then(function (response) { if (response.status === 201) { router.push(`/draft/${response.data.id}/`); } }) .catch(function (error) { // 直接打印error.response.data就能看到DRF返回的具体字段校验错误,不需要特意去Postman复现withCredentials场景 console.log(error.response.data); console.log(error.toJSON()); }); };
效果验证
改完后两个需求会自动生效:
- 前端发POST创建文档时不需要传任何用户标识,后端会自动把新建文档的所有者绑定为当前登录用户;
- 前端发GET请求拉取文档列表时,后端只会返回当前用户自己创建的文档,无法看到其他人的内容。
内容的提问来源于stack exchange,提问作者Tyler Kim
相关产品推荐
相关产品推荐

