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

React与Django Rest API文件上传时POST 400错误的排查与解决

400 Bad Request错误排查与解决方案:React + Django Rest Framework用户表单提交问题

问题根源分析

结合代码细节,导致POST请求400错误的核心原因有以下几点:

  • React文件输入处理错误:文件输入框的onChange事件错误使用e.target.value获取文件,实际应获取e.target.files数组,导致传递给后端的文件数据无效。
  • Fetch请求头设置错误:手动指定Content-Type: multipart/form-data,但FormData会自动生成包含boundary的正确请求头,手动设置会丢失boundary,导致Django无法解析表单数据。
  • Django用户创建逻辑缺失必填参数:自定义UserManager的create_user方法要求传入password参数,但前端未传递、序列化器也未处理该参数,触发校验失败。
  • React受控组件文件输入值处理不当:文件输入框的value属性设置为字符串,不符合文件输入的受控组件规范,导致无法正确更新或清空文件选择。

解决方案

React端修复

1. 修正UserForm的文件输入处理逻辑

修改HandleChange函数,针对文件输入单独处理,存储files数组而非字符串值:

const HandleChange = (e) => {
    const SelectedField = e.target.name;
    let SelectedValue;
    // 区分文件输入和普通输入
    if (e.target.type === 'file') {
        SelectedValue = e.target.files;
    } else {
        SelectedValue = e.target.value;
    }
    setUser(prevState => ({ ...prevState, [SelectedField]: SelectedValue }))
}

同时调整初始state和文件输入框的value属性:

// 初始state将profileImage设为空数组
const [user, setUser] = useState({
    username: "",
    profileImage: [],
    email: "",
});

// 文件输入框的value属性修改为文件名(或空)
<input type="file" name='profileImage' onChange={HandleChange} value={profileImage.length > 0 ? profileImage[0].name : ''} />

2. 移除Fetch请求的手动Content-Type设置

FormData会自动生成包含boundary的正确请求头,删除手动设置的Content-Type:

fetch(URL, {
    method: "POST",
    // 移除该行:headers: {'Content-Type' : 'multipart/form-data'},
    body: formData
})

3. 新增密码输入(可选,根据业务需求)

如果用户创建需要密码,在UserForm中添加密码输入框:

<input type="password" name='password' onChange={HandleChange} required placeholder="设置密码" />

并在addUSer方法中将密码加入FormData:

formData.append('password', user.password);

Django端修复

1. 修正序列化器的用户创建逻辑

确保调用create_user时传入password参数,并在序列化器中添加password字段:

class UserSerializer(ModelSerializer):
    class Meta:
        model = User
        fields = (
            'id',
            "username",
            "email",
            "profileImage",
            "password",  # 新增password字段
        )
        extra_kwargs = {
           "password": {"write_only":True, "style":{"input_type": "password"}} 
        }

    def create(self, validate_data):
        password = validate_data.get('password')
        if not password:
            raise serializers.ValidationError("密码为必填项")
        user = User.objects.create_user(
            username=validate_data['username'],
            email=validate_data['email'],
            password=password,  # 传递password参数
            profileImage=validate_data.get('profileImage'),
        )
        return user

2. 可选:调整UserManager允许无密码创建(不推荐生产环境)

如果业务场景不需要密码,可修改create_user方法让密码参数可选:

def create_user(self, username, email, password=None, **extra_fields):
    email = self.normalize_email(email)
    user = self.model(username=username, email=email, **extra_fields)
    if password:
        user.set_password(password)
    user.save()
    return user

3. 配置CORS跨域(若需跨域请求)

安装django-cors-headers并配置允许React开发服务器跨域:

# settings.py
INSTALLED_APPS = [
    ...
    'corsheaders',
]
MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    ...
]
CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",  # React开发服务器地址
]

测试建议

  • 打开浏览器开发者工具的Network标签,查看POST请求的FormData,确认所有字段(包括文件)都正确传递。
  • 查看Django控制台的错误日志,日志会显示具体的400错误原因(如缺少参数、文件解析失败等),这是排查问题的关键。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 21:50:40