如何使用 React.js + Django Rest 框架保存带有表单提交的 blob 文件



我正在尝试使用react-image-crop提交在react应用程序中生成的裁剪图像,并使用Axios将其保存到Django Rest Api中。

该应用程序在前端使用React,Redux和Axios,在后端使用Django Rest Framework。

表单在没有文件的情况下提交得很好,并且在没有添加文件代码的情况下保存在 django 中。

现在,文件已添加到表单提交中,服务器将返回 400 错误。

我怀疑我没有以正确的格式将 blob 提交给 django 服务器,但我不确定如何继续。

更新:我已经使用下面的 axios 将 blob url 转换为 blob,现在我正在尝试一个可以提交给 django rest api 的文件。 表单在没有文件的情况下提交到 django rest API,但是当文件添加到表单提交中时,我收到 400 错误。 我已经更新了代码以反映我的最新集成。我已经包含了将标题设置为多部分/表单数据的代码。 错误似乎出在下面的 onSubmit(( 方法的文件转换过程中。

这是我的相关代码: 导入反应图像裁剪库。

// Cropper
import 'react-image-crop/dist/ReactCrop.css';
import ReactCrop from 'react-image-crop';

反应钩子内部的函数:

const AdCreator = ({ addFBFeedAd }) => {
const [title, setTitle] = useState('');
const [headline, setHeadline] = useState('');
const [ad_text, setAdText] = useState('');
const cropper = useRef();

// Cropper
const [upImg, setUpImg] = useState();
const imgRef = useRef(null);
const [crop, setCrop] = useState({ unit: '%', width: 30, aspect: 1.91 / 1 });
const [previewUrl, setPreviewUrl] = useState();
const onSelectFile = e => {
if (e.target.files && e.target.files.length > 0) {
const reader = new FileReader();
reader.addEventListener('load', () => setUpImg(reader.result));
reader.readAsDataURL(e.target.files[0]);
}
};
const onLoad = useCallback(img => {
imgRef.current = img;
}, []);
const makeClientCrop = async crop => {
if (imgRef.current && crop.width && crop.height) {
createCropPreview(imgRef.current, crop, 'newFile.jpeg');
}
};
const makePostCrop = async crop => {
if (imgRef.current && crop.width && crop.height) {
createCropPreview(imgRef.current, crop, 'newFile.jpeg');
}
};
const createCropPreview = async (image, crop, fileName) => {
const canvas = document.createElement('canvas');
const scaleX = image.naturalWidth / image.width;
const scaleY = image.naturalHeight / image.height;
canvas.width = crop.width;
canvas.height = crop.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(
image,
crop.x * scaleX,
crop.y * scaleY,
crop.width * scaleX,
crop.height * scaleY,
0,
0,
crop.width,
crop.height
);
return new Promise((resolve, reject) => {
canvas.toBlob(blob => {
if (!blob) {
reject(new Error('Canvas is empty'));
return;
}
blob.name = fileName;
window.URL.revokeObjectURL(previewUrl);
setPreviewUrl(window.URL.createObjectURL(blob));
}, 'image/jpeg');
});
};
const onSubmit = (e) => {
e.preventDefault();
const config = { responseType: 'blob' };
let file = axios.get(previewUrl, config).then(response => {
new File([response.data], title, {type:"image/jpg", lastModified:new Date()});       
}); 
let formData = new FormData();
formData.append('title', title);
formData.append('headline', headline);
formData.append('ad_text', ad_text);
formData.append('file', file);
addFBFeedAd(formData);

};
return (

表格部分:

<form method="post" id='uploadForm'>                  
<div className="input-field">
<label for="id_file">Upload Your Image</label>
<br/>
{/* {{form.file}} */}
</div>
<div>
<div>
<input type="file" accept="image/*" onChange={onSelectFile} />
</div>
<ReactCrop
src={upImg}
onImageLoaded={onLoad}
crop={crop}
onChange={c => setCrop(c)}
onComplete={makeClientCrop}
ref={cropper}
/>
{previewUrl && <img alt="Crop preview" src={previewUrl} />}
</div>
<button className="btn darken-2 white-text btn-large teal btn-extend" id='savePhoto' onClick={onSubmit} value="Save Ad">Save Ad</button>
</form>

这是Axios呼叫:

export const addFBFeedAd = (fbFeedAd) => (dispatch, getState) => {
setLoading();
axios
.post(`http://localhost:8000/api/fb-feed-ads/`, fbFeedAd, tokenMultiPartConfig(getState))
.then((res) => {
dispatch(createMessage({ addFBFeedAd: 'Ad Added' }));
dispatch({
type: SAVE_AD,
payload: res,
});
})
.catch((err) => dispatch(returnErrors(err)));
}

这是我将标题设置为多部分表单数据的地方

export const tokenMultiPartConfig = (getState) => {
// Get token from state
const token = getState().auth.token;
// Headers
const config = {
headers: {
"Content-type": "multipart/form-data",
},
};
// If token, add to headers config
if (token) {
config.headers['Authorization'] = `Token ${token}`;
}
return config;
};

模型:

class FB_Feed_Ad(models.Model):
title = models.CharField(max_length=100, blank=True)
headline = models.CharField(max_length=25, blank=True)
ad_text = models.CharField(max_length=125, blank=True)
file = models.ImageField(upload_to='photos/%Y/%m/%d/', blank=True)

裁剪预览斑点:

blob:http://localhost:3000/27bb58e5-4d90-481d-86ab-7baa717cc023

我在 axios 调用后控制台.log裁剪的图像。

File:  
Promise {<pending>}
__proto__: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: undefined
AdCreator.js:169 formData: 
FormData {}
__proto__: FormData

如您所见,我正在尝试提交由反应图像裁剪器生成的 blob 图像文件,作为提交表单时表单数据的一部分。 我想将裁剪后的图像保存到 Django Rest API。
有什么建议吗?

你应该把它作为"Content-Type": "multipart/form-data"发送到django imageField。因此,您应该适当地转换 blob 文件:

let cropImg = this.$refs.cropper.getCroppedCanvas().toDataURL();
let arr = this.cropImg.split(","),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
let imageCrop = new File([u8arr], 'imagename', { type: mime });
const fd = new FormData();
fd.append("avatar", imageCrop);
// send fd to axios post method. 
// You should pass in post request "Content-Type": "multipart/form-data" inside headers.

最新更新