Upload file Vue 3 and Django REST - javascript

I dont get if i work with request correctly, after upload all files is 1 KB and i cant open them. How to create correct file? If i save file as .doc i can see:
------WebKitFormBoundaryt3UjlK5SVq8hgppA
Content-Disposition: form-data; name="file"
[object FileList]
------WebKitFormBoundaryt3UjlK5SVq8hgppA--
So my functions to submit in js file:
async submitFiles() {
let formData = new FormData();
formData.append('file', this.file);
console.log(this.file)
axios.put(`/api/v1/myapp/upload/${this.file[0].name}`,
formData,
{
headers: {
'Content-Disposition': 'attachment',
'X-CSRFToken': await this.getCsrfToken(),
},
}
).then(function () {
console.log('SUCCESS!!');
})
.catch(function () {
console.log('FAILURE!!');
});
},
To handle change of file in form
fileChanged(file) {
this.file = file.target.files
},
And finally my view.py
class FileUploadView(APIView):
parser_classes = [FileUploadParser]
def put(self, request, filename, format=None):
file_obj = request.data['file']
handle_uploaded_file(file_obj)
return Response({'received data': request.data})
Where
def handle_uploaded_file(f):
with open('path/to/my/folder/' + str(f.name), 'wb+') as destination:
for chunk in f.chunks():
destination.write(chunk)

[object FileList]
Oh, you serialized the whole FileList.
Change to: formData.append('file', this.file[0]);
If this won't work you may need to read the file's content.
Edit: it should be enough, according to MDN:
The field's value. This can be a USVString or Blob (including subclasses such as File). If none of these are specified the value is converted to a string.

Related

Laravel 9 and Javascript: how to download a file returned from Storage::download()

DISCLAIMER: Before creating this question, I've checked here, here and here, and also checked Laravel docs.
Context
Laravel 9 full-stack
No JS framework on front-end, which means I'm using vanilla JS
The folders on Storage are setted like this:
storage
app
public
folder1
folder1A
folder1B
folder1C
etc
The files stored in each folder1X are .pdf format and I don't know its names.
No folders are empty, nor with invalid/corrupted files.
The problem
I have a FileController.php to download files that are inside a folder1X/ directory. The method to download it is as follows:
public function downloadFileFromStorage(Request $request): mixed
{
$dirpath = $request->dirpath; // dirpath = public/folder1/folder1X.
$files = Storage::allFiles($dirpath);
return response()->download(storage_path('app\\' . $files[0]));
}
(Note: dirpath is sent in a axios request by client and is also fetched from database on a previous request)
My Javascript CLI needs to enable the download of this file. The download is enabled by clicking on a button. The button calls downloadPDF(dirpath) which works as follows:
function downloadPDF(dirpath) {
axios.post('/download-pdf-file', { dirpath })
.then(
success => {
const url = success.data
const a = document.createElement('a')
a.download = 'file.pdf'
a.href = url
a.click()
},
error => {
console.log(error.response)
}
)
}
But, when I run this function, I get a about:blank#blocked error.
Attempts
Changed the a HTML DOM approach to a window.open(url) on client;
Changed response() to Storage::download($files[0], 'file-name.pdf'), and with this I also tried using Blob on client as follows:
success => {
const blob = new Blob([success.data], { type: 'application/pdf' })
const fileURL = URL.createObjectURL(blob)
window.openURL(fileURL)
},
Also mixed Blob with the a HTML DOM approach;
Changed storage_path argument to /app/public/ before concatenating to $files[0].
UPDATE
Following tips from #BenGooding and #cengsemihsahin, I changed files to the following:
JS
// FileDownload is imported on a require() at the code beginning
function downloadPDF(dirpath) {
axios({
url: '/download-pdf-file',
method: 'GET',
responseType: 'blob',
options: {
body: { dirpath }
}
}).then(
success => {
FileDownload(success.data, 'nota-fiscal.pdf')
}
)
}
PHP:
public function downloadFileFromStorage(Request $request): mixed
{
$dirpath = $request->dirpath; // dirpath = public/folder1/folder1X.
$files = Storage::allFiles($dirpath);
return Storage::download($files[0], 'filename.pdf');
}
and now it downloads a corrupted PDF that can't be opened.
Finally found the issue, and it was here:
axios({
url: '/download-pdf-file',
method: 'GET',
responseType: 'blob',
options: { // here
body: { dirpath } // here
}
})
Laravel's Request arrow operator -> can't fetch a GET body sent through options (At least, not on $request->key fashion; see more about it here) thus making me download a corrupted file - it wasn't fetching any file on Laravel as it didn't get any path at all.
Here is the solution I came with:
As I want to get a file in a route that doesn't change except for the 1X at folder1X, I'm processing the path obtained and sending the 1X as a GET query param:
let folderNumber = dirpath.split('/')
folderNumber = folderNumber[folderNumber.length].replaceAll('/', '')
axios({
url: '/download-pdf-file?folder=',
method: 'GET',
responseType: 'blob'
})
This way I don't pass the whole path to back-end and it's possible to get folderNumber by using $request->query():
public function downloadFileFromStorage(Request $request): mixed
{
$folderNumber = $request->query('folderNumber');
$folderPath = '/public/folder1/folder' . $folderNumber . '/';
$files = Storage::allFiles($folderPath);
return Storage::download($files[0], 'file-name.pdf');
}
In a nutshell:
To download files, use GET requests;
To send arguments within GET requests, use query parameters and fetch them with $request->query('keyname') (or find out another way. Good luck!);

How do you get the file path of an <input> file?

I'm trying to use the Axios POST method to upload a file to Pinata IPFS:
FRONT-END:
<body>
<input type="file" id="file-upload" ></input>
<script> let file = document.getElementById("file-upload").value;</script>
</body>
FILE GETS SENT TO BACKEND THROUGH SOCKET.IO:
pin = (pinataApiKey, pinataSecretApiKey, file) => {
url = `https://api.pinata.cloud/pinning/pinFileToIPFS`;
const data = new FormData();
data.append("file", fs.createReadStream(`${file}`));
return axios.post(url, data, {
maxBodyLength: "Infinity",
headers: {
"Content-Type": `multipart/form-data; boundary=${data._boundary}`,
pinata_api_key: pinataApiKey,
pinata_secret_api_key: pinataSecretApiKey,
},
});
};
pin() only works when I use a local file path (i.e. C:/Users/anon/Desktop/project/untitled.png). If I try to use file.value (whose path is "C:\fakepath\testImage.jpeg"), the code doesn't work. I need the actual path of the uploaded file.
What do you get when you log file??
Perhaps you need to put this:
let file = document.getElementById("file-upload").value;
inside your js file:
And probably the best thing is to do some validations before the file is uploaded.

How to upload & pass variables to the same php file

I try uploading a text file from client desktop to a server. A web page is built on js like this:
const usunDane = document.getElementById('cbxUsunDane').checked;
const nadpiszDane = document.getElementById('cbxNadpiszDane').checked;
formData.append("file", selectedFile);
formData.append("usunDane", usunDane);
formData.append("nadpiszDane", nadpiszDane);
await fetch('php/produktyUpload.php', {
method: "POST",
body: formData
});
I added usunDane and nadpiszDane hoping to pass it into produktyUpload.php file, however I do not know how to properly read it. I tried this:
if ($_FILES['nadpiszDane']=="true") {
error_log("UPDATE ", 3, "/var/www/html/errors.log");
} else {
error_log("INSERT ", 3, "/var/www/html/errors.log");
}
... but this does not work.
When I look at the browser I can see that both variables are passed to php file...
*
Content-Disposition: form-data; name="usunDane"
true
Content-Disposition: form-data; name="nadpiszDane"
true
*
Could someone please advice on how to pass/read these variables?

Python server store csv file sent from React client

As the title says, I have a client page that allows the user to upload files, they will always be .csv files.
export const sendFiles = async (files) => {
let form = new FormData();
form.append("arrFile", files);
await axios
.post(local + "/read-files", form, {
headers: { "Content-Type": "multipart/form-data" },
})
.then((res) => {
return res.data;
})
.catch((err) => {
return err;
});
};
This is the code I have for sending the files, using FormData. The files is an array of File objects. It can have just one or many. The files array on console looks like this
Array [ File ]
0: File { name: "filename.csv", lastModified: 1624560497968, size: 1244, … }
​​ lastModified: 1624560497968
name: "filename.csv"
size: 1244
type: "application/vnd.ms-excel"
webkitRelativePath: ""
<prototype>: FilePrototype { name: Getter, lastModified: Getter, webkitRelativePath: Getter, … }
length: 1
<prototype>: Array []
I'm sending this to my Python Flask server
#app.route("/read-files", methods=['POST'])
def read_files():
if request.method == 'POST':
form = dict(request.form)
return
I found that the FormData is stored in request.form, but when I print this data on Python it shows as such.
{'arrFile': '[object File],[object File],[object File],[object File],[object File],[object File],[object File]'}
The files seem to be in a string separated by commas. What I'd like to do is be able to store these files and their content, and be able to read them afterwards. Is this possible?
if {your_file_name} in request.files:
file = request.files['your_file_name']

Display raw image content-type in vue.js

I am retrieving an image from a REST API via an HTTP GET with a request body.
I've managed to check the returned content via this test using node.js and chai.js:
expect(res).to.have.header('Content-Type', 'image/jpeg');
expect(res).to.have.header('Access-Control-Allow-Origin', '*');
expect(res).to.have.header('Access-Control-Allow-Headers', 'Access-Control-Allow-Headers, Origin, X-Requested-With, Content-Type, Accept, Authorization');
expect(res).to.have.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, HEAD');
expect(res).to.have.status(200);
expect(res.body).to.be.instanceof(Buffer); // the image content
In the vue.js file I was used to attach an image to an <img ...> HTML tag like this:
<img v-bind:src="urlImg">
Then specifying in the javascript part the URL like this:
# this string representing the URL is returned by a function
this.urlImg = 'http://example.com/my_img.jpeg'
but in this case I am not able to provide the URL because the HTTP GET is expecting a body to return the image with content type image/jpeg.
I am not even sure this is possible and I may be misunderstanding how the content type image/jpeg is supposed to work. How do I do this in vue.js? Is it possible at all? Is there a way to check the image content of this HTTP response as with stuff like Postman (Chrome app) I can not inspect the response pretending to treat it as text/Json.
EDIT
Regarding the accepted answer: the last proposed solution (UPDATE 2) worked for me (using HTTP POST providing a JSON body for the request). Make sure you use axios (https://github.com/axios/axios) to perform the HTTP requests (you can import it in the <script> part of the Vue file like this: import axios from "axios";).
I was using vue-resource (https://github.com/pagekit/vue-resource) pretending it was the same as axios, but it is not and it took me a while to realize it.
If you already have Buffer of your image you can specify pre-defined link in your client app:
this.urlImg = '/my/url/to/get/dynamic/image';
And define route to send image from server to client (for Express):
server.get("my/url/to/get/dynamic/image", function(req, res) {
var myBuffer = yourFunctionReturnsBuffer();
res.writeHead(200, {
'Content-Type': 'image/jpeg',
'Content-Length': myBuffer.length
});
res.end(myBuffer);
});
Working example for Express+request: My Gist
UPDATE
Load image in browser via ajax (example below). But it is not possible to send request body for GET method with native browser XMLHttpRequest object (this is base for all ajax libraries). From MDN:
send() accepts an optional parameter which lets you specify the request's body; this is primarily used for request such as PUT. If the request method is GET or HEAD, the body parameter is ignored and request body is set to null.
var app = new Vue({
el: "#app",
data: {
// empty image
src: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
},
created() {
let config = {
// example url
url: 'https://www.gravatar.com/avatar/7ad5ab35f81ff2a439baf00829ee6a23',
method: 'GET',
responseType: 'blob'
}
axios(config)
.then((response) => {
let reader = new FileReader();
reader.readAsDataURL(response.data);
reader.onload = () => {
this.src = reader.result;
}
});
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<img :src="src" alt="">
</div>
UPDATE 2
Decode image as array buffer
var app = new Vue({
el: "#app",
data: {
// empty image
src: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
},
created() {
let config = {
// example url
url: 'https://www.gravatar.com/avatar/7ad5ab35f81ff2a439baf00829ee6a23',
method: 'GET',
responseType: 'arraybuffer'
}
axios(config)
.then((response) => {
var bytes = new Uint8Array(response.data);
var binary = bytes.reduce((data, b) => data += String.fromCharCode(b), '');
this.src = "data:image/jpeg;base64," + btoa(binary);
});
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<img :src="src" alt="">
</div>

Categories