How to send files to Django REST Framework from React? - reactjs

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)

Related

Error sending PDF from ReactJS to Flask app

I'm trying to send a PDF from a frontend react app to a backend flask app using fetch. The idea is a user uploads a PDF on the frontend and sends to a backend that parses it and returns an edited version in JSON format.
Frontend:
function App() {
const [file, setFile] = useState(null);
const [data, setData] = useState();
const upload = (event) => {
setFile(event.target.files[0]);
};
const submit = () => {
const formData = new FormData();
formData.append("file", file);
fetch("http://localhost:5000/parser", {
method: "POST",
mode: "no-cors",
body: formData,
})
.then((res) => res.json())
.then((res) => {
setData(res);
console.log(res);
});
};
useEffect(() => {
if (file) {
submit();
}
}, [file]);
return (
<div className="App">
{" "}
<input type="file" accept="application/pdf" onChange={upload} />
<input type="submit" onClick={submit} />
</div>
);
}
export default App;
Backend:
app = Flask(__name__)
CORS(app)
#app.route("/parser", methods=['GET', 'POST'])
def parse():
file = request.files['file']
file.save(secure_filename("secure.pdf"))
fp = open("secure.pdf", 'rb')
data = convert(fp, 0)
response = make_response(data)
response.headers["Content-Type"] = "application/json"
response.headers["Access-Control-Allow-Origin"] = "http://localhost:5173"
return response
if __name__ == "__main__":
# for production change debug to False
app.run(debug=True)
The flask server is running with this error:
werkzeug.exceptions.BadRequestKeyError: 400 Bad Request: The browser (or proxy) sent a request that this server could not understand.
KeyError: 'file'
I'm not sure what I'm doing wrong here. As of now when I hit submit on the front end to send the PDF, the backend saves it to the local repo. So the PDF is getting through but for some reason there's an issue on the way back it seems.
Is there a better way to do this? To send a PDF and return a JSON? The python function I have works when I run it with a local PDF in the dir, but I need these two apps to work dynamically with each other.
The error makes even less sense to me because I'm able to save the file to the local repo, but am still getting an error seemingly on that line: file = request.files['file']
You cannot send the pdf as a json file until it get transformed. You should change it to b64 kind of transformation as temporary file(encoding and decoding) like this.

Django API rejects file

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')

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

how to send images through axios using gridfs?

how can i send a file/image in reactjs uploaded through an to the backend, using axios?
the simple input form is this :
<form className={classes.form} onSubmit={handleSubmit(submitFunc)}>
<Grid container spacing={2}>
<Grid item xs={12}>
<input
type="file"
accept="image/*"
alt="file"
name="file"
id="file"
/>
</Grid>
</Grid>
<Button
type="submit"
>
Add
</Button>
</form>
the submit function:
try {
await axios
.post(`http://localhost:5000/addPic`, data, {
headers: {
accept: "application/json",
"Accept-Language": "en-US,en;q=0.8",
"Content-Type": `multipart/form-data`,
},
})
}
);
} catch (error) {
console.log("error");
}
i tried this but it's not working, and i dont know why, because when i use postman to send an image to the same api, it works :
also when i use a view engine and use a form with method="POST" in the backend, it works!
here's the api code :
const conn = mongoose.createConnection(mongoURI);
let gfs;
conn.once("open", () => {
// Init stream
gfs = Grid(conn.db, mongoose.mongo);
gfs.collection("uploads");
});
let tempNameFile;
const storage = new GridFsStorage({
url: mongoURI,
file: (req, file) => {
return new Promise((resolve, reject) => {
crypto.randomBytes(16, (err, buf) => {
if (err) {
return reject(err);
}
const filename = buf.toString("hex") + path.extname(file.originalname);
const fileInfo = {
filename: filename,
bucketName: "uploads",
};
tempNameFile = filename;
console.log(tempNameFile);
resolve(fileInfo);
});
});
},
});
const upload = multer({ storage });
router.post("/", upload.single("file"), async (req, res) => {
console.log(tempNameFile);
res.send("good to go");
});
to sum up all of this, my question is how can i upload an image to the frontend using a simple input and send it through axios, the same way postman sends an image to the backend to be handled by gridfs and stored in the mongodb database
Since Postman worked, your backend is setup properly. Now on to your frontend.
Axios handles multipart form data if your data is an instance of FormData.
In your component you can set a state variable to hold the selected file
const [selectedFile, setSelectedFile] = useState(null);
Add an onInput to your <input /> field like so:
<input onInput={e => setSelectedFile(e.target.files[0])} />
In the submit function, wrap the file in a FormData and submit with Axios
try {
const data = new FormData();
data.append("file", selectedFile);
await axios.post(`http://localhost:5000/addPic`, data);
/* Do something if upload was successful */
} catch (error) {
console.log("error");
}

POST 400 (BAD REQUEST) when uploading file between React and Flask

Keep getting a 400 error when uploading an excel file in my react front end and passing it to a flask api.
The flask backend route is as such:
def post(self):
name = request.form['name']
file = request.files['file']
(....do stuff with file and name)
The react API call is as follows:
export const uploadFile = async (file) => {
const data = new FormData()
data.append("file", file)
data.append("name", 'temp')
const api_url = "http://localhost:5000/uploadFile"
const settings = {
method: "POST",
body: data
}
try {
const response = await fetch(api_url, settings)
const result = await response.json()
if (result.message === 'OK') {
return result
}
} catch (error) {
return "Unable to load file"
}
}
Interesting the NAME field is getting received by Flask but the file object for some reason is not being processed by Flask.
I've tried adding a multi-part/formdata Content-Type in the React Fetch API call as well, but that doesn't help either.
Figured out the issue.
Turns out you need to pass the [0] following in the React call:
data.append("file", file[0])
I was trying to post an image file to Flask just using a vanilla JS fetch call, and #Anubhav's answer helped me realise that I needed to use FormData, rather than just posting the file/blob as the body. In case someone is looking for some full, working example code:
<input type="file" id="fileEl"/>
let formData = new FormData();
formData.append("file", fileEl.files[0]);
let response = await fetch("http://example.com/upload", {
method: "POST",
body: formData,
}).then(r => r.json());

Resources