基于Django+React的YouTube克隆:视频上传后遇Bad Request错误
Django + React YouTube克隆项目:前端视频上传报Bad Request问题
开发Django和React的YouTube克隆项目时,前端上传视频出现终端错误:
return (<QueryDict: {'title': ['Test Video Creation'], 'user': ['1'], 'description': ['Is it working?'], 'image': [<TemporaryUploadedFile: attachment.png (image/png)>], 'video': [<TemporaryUploadedFile: video-for-fatube.mp4 (video/mp4)>]}>Bad Request: /api/admin/create/ )
但后端配置无异常,Postman发送POST请求可成功创建视频。
以下是前端创建视频的create.js原代码:
import React, { useState } from 'react'; import axiosInstance from '../../axios'; import { useHistory } from 'react-router-dom'; //MaterialUI import Avatar from '@material-ui/core/Avatar'; import axios from "axios"; import Button from '@material-ui/core/Button'; import CssBaseline from '@material-ui/core/CssBaseline'; import TextField from '@material-ui/core/TextField'; import Grid from '@material-ui/core/Grid'; import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import Container from '@material-ui/core/Container'; import IconButton from '@material-ui/core/IconButton'; import PhotoCamera from '@material-ui/icons/PhotoCamera'; const useStyles = makeStyles((theme) => ({ paper: { marginTop: theme.spacing(8), display: 'flex', flexDirection: 'column', alignItems: 'center', }, avatar: { margin: theme.spacing(1), backgroundColor: theme.palette.secondary.main, }, form: { width: '100%', // Fix IE 11 issue. marginTop: theme.spacing(3), }, submit: { margin: theme.spacing(3, 0, 2), }, })); export default function Create() { const history = useHistory(); const initialFormData = Object.freeze({ title: '', description: '', }); const [videoData, updateFormData] = useState(initialFormData); const [videoimage, setVideoImage] = useState(null); const [videovideo, setVideoVideo] = useState(null); const handleChange = (e) => { if ([e.target.name] == 'image') { setVideoImage({ image: e.target.files, }); console.log(e.target.files); } if ([e.target.name] == 'video') { setVideoVideo({ video: e.target.files, }); console.log(e.target.files); } if ([e.target.name] == 'title') { updateFormData({ ...videoData, [e.target.name]: e.target.value.trim(), }); } else { updateFormData({ ...videoData, [e.target.name]: e.target.value.trim(), }); } }; const handleSubmit = (e) => { const config = { headers: { 'Content-Type': 'multipart/form-data' } }; const URL = 'http://127.0.0.1:8000/api/admin/create/'; let formData = new FormData(); formData.append('title', videoData.title); formData.append('user', 1); formData.append('description', videoData.description); formData.append('image', videoimage.image[0]); formData.append('video', videovideo.video[0]); axios .post(URL, formData, config) .then((res) => { console.log(res.data); }) .catch((err) => console.log(err)); }; const classes = useStyles(); return ( <Container component="main" maxWidth="xs"> <CssBaseline /> <div className={classes.paper}> <Avatar className={classes.avatar}></Avatar> <Typography component="h1" variant="h5"> Create New Video </Typography> <form className={classes.form} noValidate> <Grid container spacing={2}> <Grid item xs={12}> <TextField variant="outlined" required fullWidth id="title" label="Video Title" name="title" autoComplete="title" onChange={handleChange} /> </Grid> <Grid item xs={12}> <TextField variant="outlined" required fullWidth id="description" label="Video Description" name="description" autoComplete="description" onChange={handleChange} multiline rows={4} /> </Grid> <input accept="image/*" className={classes.input} id="video-image" onChange={handleChange} name="image" type="file" /> <input accept="video/*" className={classes.input} id="video-video" onChange={handleChange} name="video" type="file" /> </Grid> <Button type="submit" fullWidth variant="contained" color="primary" className={classes.submit} onClick={handleSubmit} > Create Video </Button> </form> </div> </Container> ); }
问题根源
- 表单默认提交行为未阻止:按钮为
type="submit",点击会触发表单默认的application/x-www-form-urlencoded提交,与axios的multipart/form-data请求冲突,导致后端解析异常。 - 手动设置Content-Type错误:手动指定
multipart/form-data会丢失自动生成的boundary标识,后端无法正确解析文件。 - 空值未处理:若用户未选择图片/视频,直接访问
videoimage.image[0]会抛出错误,中断请求。 - handleChange逻辑冗余:重复的表单字段更新逻辑可简化。
修复后的代码
1. 优化handleChange函数
const handleChange = (e) => { const { name, value, files } = e.target; if (name === 'image') { setVideoImage({ image: files }); } else if (name === 'video') { setVideoVideo({ video: files }); } else { updateFormData(prev => ({ ...prev, [name]: value.trim() })); } };
2. 修复handleSubmit函数
const handleSubmit = (e) => { e.preventDefault(); // 阻止表单默认提交 const URL = 'http://127.0.0.1:8000/api/admin/create/'; let formData = new FormData(); formData.append('title', videoData.title); formData.append('user', 1); formData.append('description', videoData.description); // 空值判断,避免未选文件时报错 if (videoimage?.image?.[0]) { formData.append('image', videoimage.image[0]); } if (videovideo?.video?.[0]) { formData.append('video', videovideo.video[0]); } axios .post(URL, formData) // axios自动处理Content-Type(含boundary) .then((res) => { console.log(res.data); history.push('/'); // 可选:创建成功后跳转首页 }) .catch((err) => console.error(err.response?.data || err)); // 打印详细错误信息 };
3. 调整文件输入框布局(可选)
将文件输入框包裹在Grid组件中,保证布局一致性:
<Grid item xs={12}> <input accept="image/*" className={classes.input} id="video-image" onChange={handleChange} name="image" type="file" /> </Grid> <Grid item xs={12}> <input accept="video/*" className={classes.input} id="video-video" onChange={handleChange} name="video" type="file" /> </Grid>
验证修复
修改后重新上传视频,后端应能正确解析FormData,完成视频创建。若仍有问题,可查看err.response.data获取后端返回的具体错误信息,进一步排查。
内容的提问来源于stack exchange,提问作者PHILLIPP APETENOK
相关产品推荐
相关产品推荐

