Django API rejects file - reactjs

I'm trying to send a csv file to my Django API and response with process data, but when I send it, I get the error:
django.utils.datastructures.MultiValueDictKeyError: 'file'
this is my react code:
import { useState } from 'react';
import './App.css';
function App() {
const [file, setFile] = useState(null);
const uploadFiles = e =>{
setFile(e);
}
const insertFile = async() => {
const f = new FormData();
f.append("file", file);
await fetch(
api,
{
method: 'POST',
headers: { 'content-type': 'multipart/form-data' },
body: f,
})
.then((response) => response.json())
.then((data)=>{
console.log(data);
})
.catch(error=>{
console.log(error);
});
}
return (
<>
<input type="file" name="file" onChange={(e)=>uploadFiles(e.target.files)}/>
<button onClick={()=>insertFile()}>Insertar</button>
</>
);
}
export default App;
And this is my view.py file that will process the information, for now, I just want to get the csv info in the frontend side, so the logic of how to process data doesn't matter right now.
#api_view(['POST'])
def eda(request):
file = request.FILES['file']
data = []
with open(file, encoding='utf-8') as csvf:
csvReader = csv.DictReader(csvf)
for rows in csvReader:
data.append(rows)
response = {
'csvData': data
}
return Response(response)

Seems like your files are not added in the FormData at all. It's because you are sending a list of files not a single file.
So instead of this
<input type="file" name="file" onChange={(e)=>uploadFiles(e.target.files)}/>
Use this
<input type="file" name="file" onChange={(e)=>uploadFiles(e.target.files[0])}/>
and on Django side use this:
file = request.FILES.get('file') # won't raise exception
if file is None:
# show some error response

Instead of request.FILES.get('file') try using request.data.get('file')

Related

React + Django csv handle

I'm working on a Data Mining app with React and Django, I kind of understand how to send the file to Django, but how do I read the file, apply an algorithm and return the process data to react for showing it? The objective of the app is to read a differente csv file each time, so, I don't need to create models, don't even need to store the data, just handle the information.
I've seen a lot of tutorials, but everyone make use of a database, is there a method to process the file without saving anything, just processing and return the process data for create graphs and stuff? how an I do that?
This is my attempt with a react component for sending the file to django, but now, whats next? how do I read it in django? and how do I send the process data back to react?
import { useState } from "react";
function DragDropFiles(){
const [selectedFile, setSelectedFile] = useState();
const [isFilePicked, setIsFilePicked] = useState(false);
const changeHandler = (event) => {
setSelectedFile(event.target.files[0]);
setIsFilePicked(true);
}
const handleSubmission = async () =>{
const formData = new FormData();
formData.append('File', selectedFile);
let newData = await fetch(
base_url,
{
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data'
},
body: formData,
}
)
.then((response) => response.json())
.then((result) =>{
console.log('Success:', result);
})
.catch((error) =>{
console.log('Error:', error);
});
};
return(
<>
<div>
<input type="file" name="file" onChange={changeHandler} />
{isFilePicked ? (
<div>
<p>Filename: {selectedFile.name}</p>
<p>Filetype: {selectedFile.type}</p>
<p>Size in bytes: {selectedFile.size}</p>
</div>
) : (
<p>Select a file to show details</p>
)}
<div>
<button onClick={handleSubmission}>Submit</button>
</div>
</div>
</>
);
};
export default DragDropFiles;
I know that I have to import pandas, numpy and other libraries for handle data, but I mean, literally, how do I "receive" the csv file in the backend?
Thanks.

Upload file with React

I want to make a simple file upload form on the front end. Then, on the backend, I would pass the information about that file to an API.
Here is my front-end code where I call a specific function on the back end and pass the data:
import React from 'react';
import Axios from 'axios';
const Upload = () => {
// a local state to store the currently selected file.
const [selectedFile, setSelectedFile] = React.useState(null);
const handleSubmit = async (event) => {
event.preventDefault()
//Got all the Infos about my file
console.log(selectedFile)
const formData = new FormData();
formData.append("selectedFile", selectedFile);
//Empty result
console.log(formData)
Axios.get("http://localhost:3001/upload", {
//I will pass the data to a function in the backend
params: {
data: formData,
},
})
.then((Response) => {
console.log(Response)
})
.catch(function (error) {
console.log(error);
});
}
const handleFileSelect = (event) => {
setSelectedFile(event.target.files[0])
}
return (
<form onSubmit={handleSubmit}>
<input type="file" onChange={handleFileSelect}/>
<input type="submit" value="Upload File" />
</form>
)
};
export default Test
On the back-end side, a route call the method
router.get('/upload?', Upload);
Then finally the function in the backend to process
const ApiProcess = (req, res) => {
var axios = require('axios');
var data = req.query
console.log(req.query)
//All the API Stuff
}
But the problem is that I receive empty data in the Backend. What's wrong with my code?
Thanks
EDIT
On backend side I use multer and add 'app.use(multer().any())' on top of index file. That help cause now I cant access in backend to a simple formData. Now my function that receive the data log this '[Object: null prototype] {}'
Any idea ?
This is because your file is not getting forwarded from frontend
use FileReader instead
<input type="submit" value="Upload File" onChange={(e) =>
setFile(e.target.files)} />
const data = new FormData();
data.append(file[0])
and then you can access the file data on file[0] index and after storing the data you can forward it to the backend
there are some problems in your code.
first of all an upload request usually is a post type. and also you should send Content-Type header with your request. so:
Axios.post("http://localhost:3001/upload", formData {
headers: {
'Content-Type': 'Multipart/formData',
},
})
when you log formData it's always empty. you can use some methods like formData.keys() or formData.values() to see inside it.
Ok I got the solution. I missed a piece of middleware to process Multipart/formdata on Express Side :
const router = express.Router();
const multer = require("multer");
//Set the destination folder and Naming of the file that is upload
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/')
},
filename: function (req, file, cb) {
cb(null, file.originalname)
}
})
const upload = multer({ storage: storage })
Then I process the formData with the files
router.post('/upload', upload.array("file"),Upload);
Thanks a lot for your help

Can't upload multiple files by using Multer on React

Multer worked fine when I upload multiple images by using Postman but when I try to send files as an array from the front-end, it always return files is undefined and files is not iterable.
I think the problem is in the front-end React. How to fix this problem?
Back-end
router.post('/multer',auth ,upload.array('image'), async (req, res) => { //NOTE image is the field name
try {
const urls = []
const files = req.files;
console.log('Upload cloudinary running '+ files)
.
.
.
Front-end React
import React, {Fragment, useState, useEffect} from 'react';
const Dashboard = ({auth: { user, loading }}) => {
.
.
.
const [files, setFiles] = useState([]);
const handleChange = e => {
const file_reader = new FileReader();
const file = e.target.files[0];
let file = e.target.files[0];
file_reader.onload = () => {
setFiles([...files, { image: file_reader.result }]);
};
file_reader.readAsDataURL(file);
}
const handleSubbmitFile = (e) => {
e.preventDefault();
if(!files) return;
uploadImage(files);
}
const uploadImage = async (base64EncodedImage) => {
try {
const config = {
headers:{
'Content-Type' : 'application/json'
}
}
const body = JSON.stringify({files: base64EncodedImage});
await axios.post('/api/blogs/multer', body, config);
} catch (error) {
console.log(error);
}
}
return(
<form onSubmit={handleSubbmitFile} className="form-outline">
<input name="image" onChange={handleChange} type="file"
class="form-control-file" id="exampleFormControlFile1"
accept="image/*"/>
</form>
)
}
I found the problems and I fixed it by using formData in handleSubbmitFile and set the property filed to image by using append.
const handleSubbmitFile = (e) => {
e.preventDefault(); //NOTE prevent from reload the page
let formData = new FormData();
for(var i = 0 ; i < files.length; i++){
formData.append('image',files[i])
}
if(!files) return;
uploadImage(formData);
}
Another problems is that using Json.stringify() before using axios.
I didn't use Json.stringify() to convert formData before sending it to back-end via axios. As a result, Multer work well without problems
const uploadImage = async (formData) => {
try {
const config = {
headers:{
'Content-Type' : 'application/json'
}
}
await axios.post('/api/blogs/multer', formData, config);
} catch (error) {
console.log(error);
}
}

using FormData() in react keeps returning null

I am building a React app and in it there's a part where i should set up a profile picture, but every time i call the api i keep getting an error that i haven't filled one of the fields for the post api to return a success response.
this is my jsx
<form>
<input type="file" name="file" onChange={(e) => this.handleFile(e)}></input>
<button type="button" onClick={(e) => this.handleUpload(e)} >send</button>
</form>
and here is the code i'm using to handle those
state = {
file: null
};
handleFile(e) {
console.log(e.target.files, "ssss");
console.log(e.target.files[0], "ssss");
let file = e.target.files
this.setState({ file: e })
}
handleUpload(e) {
let file = this.state.file
let fromdata = new FormData();
fromdata.append('image', file);
fromdata.append('name', "filedata");
const headers = {
Authorization': localStorage.getItem('api_key'),
}
const user = {
filedata: fromdata,
type: 1,
};
axios.post(`sth sth`, user, {
headers: headers
})
.then(res => {
console.log(res);
console.log(res.data);
})
}
so basically the server requires type and filedata but every time i send an api request it keeps returning me
filedata: ["The filedata field is required."]
and i can't seem to find where the problem is.
Work on your handleFile function properly and add a Content-Type/Accept header to your api request, your final code should look something like this. on your api console.log (req.file) you should see your file now reaches server successfully, or if you can provide a bit of your server code, I can be able to help further.
import React from 'react';
import axios from 'axios';
export class Test extends React.Component {
constructor(props){
super(props);
this.state = {
file : null
}
this.handleFile = this.handleFile.bind(this)
this.handleUpload = this.handleUpload.bind(this)
}
handleFile(e) {
let file = e.target.files[0]
this.setState({ file })
}
handleUpload (e) {
e.preventDefault();
let file = this.state.file
let fromdata = new FormData();
fromdata.append('image', file);
const headers = {
'Authorization': localStorage.getItem('api_key'),
'Content-Type' : 'multipart/form-data'
}
const user = {
filedata: fromdata,
type: 1,
};
axios.post(`sth sth`, user, {
headers
})
.then(res => {
console.log(res);
console.log(res.data);
})
}
render() {
return (
<form onSubmit={this.handleUpload}>
<label>File</label><br/>
<input type='file' onChange={this.handleFile}/>
<button>Send File!</button>
</form>
)
}
}
Assuming that you want to upload a single file, The problem I see is in setting the file to state. You are putting the entire response into the state in this statement: this.setState({ file: e }) change it to this.setState({ file: e.target.files[0] }).
Basically you are taking in more than just the file, you are taking in the entire response and other data that comes in when a user uploads a file using the choose file input button and because of that things can't be read well.
My practice: I would put the file into a global var instead of the state.
Usually in cases like this, its the function that doesn't complete but the main thread keeps on running, thats why variables state null or older value.
var file, fromdata;
handleFile(e) {
file = e.target.files[0];
}
handleUpload(e) {
fromdata = new FormData(); //make sure this statement completes before moving any further
fromdata.append('image', file);
fromdata.append('name', "filedata");
const headers = {
Authorization': localStorage.getItem('api_key'),
}
user = {
filedata: fromdata,
type: 1,
};
axios.post(`sth sth`, user, {
headers: headers
})
.then(res => {
console.log(res);
console.log(res.data);
})
}

How to send files to Django REST Framework from React?

I need to send arbitrary (e.g. xls) files from React-based frontend to Django REST Framework backend.
Googled and tried many code variants for couple of hours, none of them worked completely.
Here are essential parts of code:
React
1.1 Form input field
<input
type="file"
multiple={true}
accept=".xls,.xlsx,.csv,.txt"
onChange={this.handleFilesChosen}
/>
1.2 handleFilesChosen
handleFilesChosen = event => {
this.setState({
files: event.target.files
});
}
1.3 Upload click handler (authHeader is function substituting Authorization Bearer token)
handleUploadClick = event => {
let formData = new FormData();
for (let file of this.state.files) {
formData.append('files', file);
}
const csrf = this.getCookie('csrftoken');
fetch(`${API_BASE_PATH}/load-input-data/`, {
method: 'POST',
headers: authHeader({contentType: 'multipart/form-data', csrf: csrf}),
body: formData,
})
.then(result => result.json())
.catch(error => error);
}
DRF View
class LoadInputDataView(APIView):
parser_class = (MultiPartParser,)
#method_decorator(login_required)
def post(self, request, format=None):
print(request.data)
return Response(status=status.HTTP_201_CREATED)
I selected simple txt file (to make debugging easy, binary will go later) with hello world content, uploaded it and get <QueryDict: {}> in Django runserver console.
If I look at Chrome network tab, I see following empty request payload instead of real file content:
------WebKitFormBoundaryYw6ABRFkvxatzHqi
Content-Disposition: form-data; name="files"; filename="foo.txt"
Content-Type: text/plain
------WebKitFormBoundaryYw6ABRFkvxatzHqi--
Tried to remove contentType header - got 400 error with message JSON parse error (browser substitutes JSON contentType header automatically).
I'm stuck. Could anybody guide me?
Found solution. I should not set Content-Type header manually, it is set automatically with boundary option. Now Django's request.FILES work too and I could work with uploaded files from backend using code like:
class ParseInputDataView(APIView):
parser_class = (MultiPartParser,)
permission_classes = [permissions.IsAuthenticated]
def post(self, request, controller_id, format=None):
for file_entry in request.FILES.getlist('files'):
uploaded_file_name = file_entry.name
uploaded_file_content = file_entry.read()
...
I decided to maintain uniformity in the API and send the image within JSON.
In React:
const [image, setImage] = useState(null);
const handleImageChange = (e) => {
e.preventDefault();
const reader = new FileReader();
reader.onload = () => {
var blocks = reader.result.split(";");
const realData = blocks[1].split(",")[1];
setImage(realData);
};
reader.onerror = (error) => console.error(error);
reader.readAsDataURL(e.target.files[0]);
};
const onSaveHandler = () => {
fetch(`/url`, {
method: "post",
credentials: "include", // send cookie with auth
headers: {
"Content-Type": "application/json",
"X-CSRFToken": document.getElementById("csrf-token").value,
}
body: JSON.stringify({imageData: image}),
});
}
return(
<div>
<input
onChange={handleImageChange}
id="logo"
type="file"
multiple="false"
accept="image/*"
/>
<button onClick={onSaveHandler}>
SAVE
</button>
</div>);
In Django (DRF):
class CustomerViewSet(viewsets.ModelViewSet):
# override create method
def create(self, request, *args, **kwargs):
image_path = "whatever.jpg"
print('save image on disk: ' + image_path)
with open(image_path, "wb") as fh:
fh.write(base64.b64decode(request.data.get("imageData")))
return super().create(request)

Resources