React客户端向DRF服务器发送PUT请求更新数据失败求助
问题描述
React客户端向DRF服务器发起PUT请求更新数据时失败,返回序列化数据不合法的错误。创建功能正常,排查后发现问题出在文件(图片、音频)字段:GET请求获取的文件数据是字符串路径,而非File对象,导致更新时校验不通过。需要实现两个目标:
- 用户仅更新标题、描述等字段时,无需重新上传文件
- 了解如何将字符串路径转换为File对象,用于表单字段预填充
GET请求代码
const getSongGenre=async()=>{ const token=localStorage.getItem('token') const res =await fetch(`http://127.0.0.1:8000/update/${param.id}`,{ method:'GET', headers:{'Authorization':`Token ${token}`}, }) const data= await res.json() console.log(data) setSongGenre(data.genre) setSongDetail({ artist_name:data.music.artist_name, title:data.music.title, audioFile:data.music.audio, imageFile:data.music.image, description:data.music.description, genre:data.music.genre, }) formik.setFieldValue('artist_name',data.music.artist_name) formik.setFieldValue('title',data.music.title) formik.setFieldValue('audio',data.music.audio) formik.setFieldValue('image',data.music.image) formik.setFieldValue('description',data.music.description) formik.setFieldValue('genre',data.music.genre) }
PUT请求代码
const formik=useFormik({ initialValues:{ artist_name:'', title:"", description:"", genre:"", }, validationSchema:Yup.object({ artist_name:Yup.string().required(), title:Yup.string().required(), }), onSubmit:((values)=>{ const token=localStorage.getItem('token') const data=new FormData() data.append('artist_name',values.artist_name) data.append('title',values.title) data.append('audio',values.audio) data.append('image',values.image) data.append('description',values.description) data.append('genre',values.genre) console.log(data) console.log(formik.values) fetch(`http://127.0.0.1:8000/update/${param.id}`,{ method:"PUT", body:data, headers:{ 'Authorization':`Token ${token}`, }, }).then(res=>res.json()) .then(data=>{ try{ console.log(data) formik.setFieldValue('artist_name','') formik.setFieldValue('title','') formik.setFieldValue('audio','') formik.setFieldValue('image','') formik.setFieldValue('description','') formik.setFieldValue('genre','') if(data.status === false){ document.getElementById('upload_msg').style.display='block' document.getElementById('upload_msg').innerHTML=`<li style=color:red>${data.message}</li>` setTimeout(()=>{ if(document.getElementById('upload_msg').style.display=='block'){ document.getElementById('upload_msg').style.display='none' } },10000) } }catch(error){ console.error(error) } }) }), })
解决方案
一、实现无需重新上传文件即可更新其他字段
1. 修改DRF序列化器
将文件字段的required设为False,允许更新时不传文件:
from rest_framework import serializers from .models import Music class MusicSerializer(serializers.ModelSerializer): class Meta: model = Music fields = ['artist_name', 'title', 'audio', 'image', 'description', 'genre'] extra_kwargs = { 'audio': {'required': False}, 'image': {'required': False} }
2. 前端PUT请求时只传有效文件字段
在FormData中仅添加用户新上传的File对象,字符串路径直接跳过:
// 修改onSubmit中的FormData构建逻辑 const data=new FormData() data.append('artist_name',values.artist_name) data.append('title',values.title) data.append('description',values.description) data.append('genre',values.genre) // 仅当audio是File对象时才添加,字符串路径则跳过 if (values.audio instanceof File) { data.append('audio', values.audio) } // 图片字段同理处理 if (values.image instanceof File) { data.append('image', values.image) }
后端收到请求后,会只更新传入的字段,原有文件保持不变。
二、将字符串路径转换为File对象用于表单预填充
浏览器无法直接将本地路径转为File对象,可通过请求后端获取文件Blob再转换:
1. 封装路径转File的工具函数
async function urlToFile(url, filename, mimeType) { const response = await fetch(url) const blob = await response.blob() return new File([blob], filename, { type: mimeType }) }
2. 在GET请求中转换并赋值
修改getSongGenre函数,将获取到的文件路径转为File对象后再设置到Formik:
const getSongGenre=async()=>{ const token=localStorage.getItem('token') const res =await fetch(`http://127.0.0.1:8000/update/${param.id}`,{ method:'GET', headers:{'Authorization':`Token ${token}`}, }) const data= await res.json() console.log(data) setSongGenre(data.genre) // 提取文件名并转换音频路径为File对象 const audioFilename = data.music.audio.split('/').pop() const audioFile = await urlToFile(data.music.audio, audioFilename, 'audio/mpeg') // 根据实际音频类型调整MIME // 转换图片路径为File对象 const imageFilename = data.music.image.split('/').pop() const imageFile = await urlToFile(data.music.image, imageFilename, 'image/jpeg') // 根据实际图片类型调整MIME setSongDetail({ artist_name:data.music.artist_name, title:data.music.title, audioFile: audioFile, imageFile: imageFile, description:data.music.description, genre:data.music.genre, }) formik.setFieldValue('artist_name',data.music.artist_name) formik.setFieldValue('title',data.music.title) formik.setFieldValue('audio', audioFile) formik.setFieldValue('image', imageFile) formik.setFieldValue('description',data.music.description) formik.setFieldValue('genre',data.music.genre) }
注意:需根据实际文件类型设置正确的MIME类型,大文件转换会消耗带宽,需考虑性能影响。
内容的提问来源于stack exchange,提问作者miracle godwin
相关产品推荐
相关产品推荐

