How to upload files with DRF and React? - reactjs

I'm learning Django Rest Framework, everything was going well until I had to create a service to upload files to my application.As much as I read the documentation, I can't understand it.
First I want to clarify that I am not a programming expert, I am a
newbie but I am here learning more every day.
From what I've managed to understand so far:
Documents and photos are not stored in the database. These files are stored in a folder.
This is correct ?
I have a form where it allows me to upload multiple files
example:
file.txt, document.doc, photo.png etc...
My view (Frontend):
import { useState } from "react";
import axios from "axios";
const Form = () => {
const [state_files, setState_Files] = useState(null);
const UploadFiles = function (event) {
setState_Files(event);
};
const InsertFiles = async function () {
const formData = new FormData();
for (let index = 0; index < state_files.length; index++) {
formData.append("files", state_files[index]);
}
await axios
.post("http://127.0.0.1:8000/api/v1/upload/", formData)
.then((response) => {
console.log(response.data);
})
.catch((error) => {
console.log(error);
});
};
return (
<>
<input
type="file"
name="files"
multiple
onChange={() => InsertFiles(event.target.files)}
/>
<button>Upload All files</button>
</>
);
};
export default Form;
Backend
url.py
path("upload/", Storage_View.as_view(), name="storage-index"),
storage/view.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser
class Storage_View(APIView):
parser_classes = [MultiPartParser]
def put(self, request, filename, format=None):
file_obj = request.data['file']
# ...
# What do I have to do here?
# ...
return Response({'received data': request.data})
Questions:
Why don't I see the button to upload files using the DFR tool? see attached files
The documentation does not explain what I have to do inside the function comment
https://www.django-rest-framework.org/api-guide/parsers/
def put(...):
How do I specify the path where you have to store the files you upload?
Please I need guidance.

1: show your models for more detail. you have to use filefield or
imagefield in your model. You can google and read more about these
fields.
2: put is for update, write your post method to save data. You don't
have to really do anything serious there. just check if
serializer.is_Valid() for request.data and if valid do
serializer.save(). the file or image will be uploaded to upload_to
folder which you define in your model and you will get the link.
3: read more about upload_to in Django. you can define this with the
model field.
I used ModelViewSet and this is how the create method looks like -
def create(self, request, format=None):
data = request.data
if isinstance(data, list): # <- is the main logic
serializer = self.get_serializer(data=request.data, many=True)
else:
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
You are passing a list from frontend and by default, Django works on dictionary, so you will have to manage this. Think of this as your go to code and write your post method.
Feel free to write more if you have doubts, Hope this helps :)

Related

Uppy/Shrine: How to retrieve presigned url for video after successful upload (using AWS S3)

I'm using Uppy for file uploads in React, with a Rails API using Shrine.
I'm trying to show a preview for an uploaded video before submitting a form. It's important to emphasize that this is specifically for a video upload, not an image. So the 'thumbnail:generated' event will not apply here.
I can't seem to find any events that uppy provides that returns a cached video preview (like thumbnail:generated does) or anything that passes back a presigned url for the uploaded file (less expected, obviously), so the only option I see is constructing the url manually. Here's what I'm currently trying for that (irrelevant code removed for brevity):
import React, { useEffect, useState } from 'react'
import AwsS3 from '#uppy/aws-s3'
import Uppy from '#uppy/core'
import axios from 'axios'
import { DragDrop } from '#uppy/react'
import { API_BASE } from '../../../api'
const constructParams = (metadata) => ([
`?X-Amz-Algorithm=${metadata['x-amz-algorithm']}`,
`&X-Amz-Credential=${metadata['x-amz-credential']}`,
`&X-Amz-Date=${metadata['x-amz-date']}`,
'&X-Amz-Expires=900',
'&X-Amz-SignedHeaders=host',
`&X-Amz-Signature=${metadata['x-amz-signature']}`,
].join('').replaceAll('/', '%2F'))
const MediaUploader = () => {
const [videoSrc, setVideoSrc] = useState('')
const uppy = new Uppy({
meta: { type: 'content' },
restrictions: {
maxNumberOfFiles: 1
},
autoProceed: true,
})
const getPresigned = async (id, type) => {
const response = await axios.get(`${API_BASE}/s3/params?filename=${id}&type=${type}`)
const { fields, url } = response.data
const params = constructParams(fields)
const presignedUrl = `${url}/${fields.key}${params}`
console.log('presignedUrl from Shrine request data: ', presignedUrl)
setVideoSrc(presignedUrl)
}
useEffect(() => {
uppy
.use(AwsS3, {
id: `AwsS3:${Math.random()}`,
companionUrl: API_BASE,
})
uppy.on('upload-success', (file, _response) => {
const { type, meta } = file
// First attempt to construct presigned URL here
const url = 'https://my-s3-bucket.s3.us-west-1.amazonaws.com'
const params = constructParams(meta)
const presignedUrl = `${url}/${meta.key}${params}`
console.log('presignedUrl from upload-success data: ', presignedUrl)
// Second attempt to construct presigned URL here
const id = meta.key.split(`${process.env.REACT_APP_ENV}/cache/`)[1]
getPresigned(id, type)
})
}, [uppy])
return (
<div className="MediaUploader">
<div className="Uppy__preview__wrapper">
<video
src={videoSrc || ''}
className="Uppy__preview"
controls
/>
</div>
{(!videoSrc || videoSrc === '') && (
<DragDrop
uppy={uppy}
className="UploadForm"
locale={{
strings: {
dropHereOr: 'Drop here or %{browse}',
browse: 'browse',
},
}}
/>
)}
</div>
)
}
export default MediaUploader
Both urls here come back with a SignatureDoesNotMatch error from AWS.
The manual construction of the url comes mainly from constructParams. I have two different implementations of this, the first of which takes the metadata directly from the uploaded file data in the 'upload-success' event, and then just concatenates a string to build the url. The second one uses getPresigned, which makes a request to my API, which points to a generated Shrine path that should return data for a presigned URL. API_BASE simply points to my Rails API. More info on the generated Shrine route here.
It's worth noting that everything works perfectly with the upload process that passes through Shrine, and after submitting the form, I'm able to get a presigned url for the video and play it without issue on the site. So I have no reason to believe Shrine is returning incorrectly signed urls.
I've compared the two presigned urls I'm manually generating in the form, with the url returned from Shrine after uploading. All 3 are identical in structure, but have different signatures. Here are those three urls:
presignedUrl from upload-success data:
https://my-s3-bucket.s3.us-west-1.amazonaws.com/development/cache/41b229fb17cbf21925d2cd907a59be25.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAW63AYCMFA4374OLC%2F20221210%2Fus-west-1%2Fs3%2Faws4_request&X-Amz-Date=20221210T132613Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=97aefd1ac7f3d42abd2c48fe3ad50b542742ad0717a51528c35f1159bfb15609
presignedUrl from Shrine request data:
https://my-s3-bucket.s3.us-west-1.amazonaws.com/development/cache/023592fb14c63a45f02c1ad89a49e5fd.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAW63AYCMFA4374OLC%2F20221210%2Fus-west-1%2Fs3%2Faws4_request&X-Amz-Date=20221210T132619Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=7171ac72f7db2b8871668f76d96d275aa6c53f71b683bcb6766ac972e549c2b3
presigned url displayed on site after form submission:
https://my-s3-bucket.s3.us-west-1.amazonaws.com/development/cache/41b229fb17cbf21925d2cd907a59be25.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAW63AYCMFA4374OLC%2F20221210%2Fus-west-1%2Fs3%2Faws4_request&X-Amz-Date=20221210T132734Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=9ecc98501866f9c5bd460369a7c2ce93901f94c19afa28144e0f99137cdc2aaf
The first two urls come back with SignatureDoesNotMatch, while the third url properly plays the video.
I'm aware the first and third urls have the same file name, while the second url does not. I'm not sure what to make of that, though, but the relevance of this is secondary to me, since that solution was more of a last ditch effort anyway.
I'm not at all attached to the current way I'm doing things. It's just the only solution I could come up with, due to lack of options. If there's a better way of going about this, I'm very open to suggestions.

TFJS: loadGraphModel from HTTP server

I am implementing an object-detection web application using React and Tensorflow JS. I converted my model to a tensorflow JS model, such that I can load it into my React application. I want to load the model using a simple HTTP endpoint, which is a Flask server currently hosting on my local machine. The Flask main file looks as follows:
from flask import Flask
from flask_cors import CORS, cross_origin
import os
app = Flask(__name__)
cors = CORS(app)
#app.route('/')
def hello_world():
return 'Hello, World!'
#app.route('/model', methods=['GET'])
def get_modeljson():
"""
Get the model.json file and return it's contents.
"""
current_dir = os.getcwd()
file_path = os.path.join(current_dir, "models", "model.json")
with open(file_path, "r") as f:
return f.read()
if __name__ == '__main__':
app.run(debug=True, host="0.0.0.0", threaded=True)
I have written a function in my React application that loads the graph model using the endpoint /model that is defined in the code above. The React function looks as follows:
import {useEffect, useState} from 'react';
import * as tf from '#tensorflow/tfjs';
import {loadGraphModel} from '#tensorflow/tfjs-converter';
function Model(props) {
const [model, setModel] = useState();
async function loadModel() {
try {
const model_url = "http://127.0.0.1:5000/model";
const result = await fetch(model_url);
const result_json = await result.json();
const model = await loadGraphModel(result_json);
console.log('model loaded...')
setModel(model);
console.log("Model correctly loaded");
} catch (err) {
console.log(err);
console.log("failed load model");
}
}
useEffect(() => {
tf.ready().then(() => {
loadModel();
});
}, []);
async function predictFunction() {
// use model to make predictions
}
return (
<Button onClick={() => {
predictFunction();
}}
/>
);
}
export default Model;
The FLASK API returns correctly the model.json file, however loadGraphModel returns the following error:
TypeError: url.startsWith is not a function
at indexedDBRouter (indexed_db.ts:215)
at router_registry.ts:95
at Array.forEach (<anonymous>)
at Function.getHandlers (router_registry.ts:94)
at Function.getLoadHandlers (router_registry.ts:84)
at Module.getLoadHandlers (router_registry.ts:110)
at GraphModel.findIOHandler (graph_model.ts:107)
at GraphModel.load (graph_model.ts:126)
at loadGraphModel (graph_model.ts:440)
at loadModel (Model.js:16)
I can not find any documentation about url.startsWith. Who sees what is going wrong here?
Going through the code I see a major issue with it, where you are trying to basically send a model.json from the backend to the frontend and then load the model from that model.json and perform inference on it. It would work but it is not efficient at all. Imagine having to do this a couple hundred times and I know the model.json file can be big in size. Instead there are two routes that you could go with:
Host the model on the backend, send the data to the backend through a POST request and then make predictions on the data from the request.
Use the model on the frontend and then make predictions on the input data from there.
There are some errors in the code which are causing the error but this is the issue that you need to fix first. If you could give me more information about the inputs you are working with I could draft up a workable solution.

ReactJS form submit this.props.history.push('/downloads') not linking through to the new page

I am trying to Push to a new page once a user has filled out a form using this.props.history.push inside the function below.
handleSubmit = async event => {
event.preventDefault()
try {
const res = await newEnquiry(this.state.formData)
this.props.history.push('/downloads')
console.log(res)
} catch (err) {
console.log(err.response.data)
}
}
The ReactJS form is working fine on the /contacts page, and submits information to my Django back-end so I know it's working OK, however I cannot get the redirect to work and it's giving me this error message.
<form onSubmit={this.handleSubmit}> is inside my form tag and that's working fine so I am pretty sure it's not a problem with my form.
Api.js
const baseUrl = '/api'
export const newEnquiry = formData => {
return axios.post(`${baseUrl}/enquiries/`, formData)
}
Views.py
class EnquiryListView(APIView):
def get(self, _request):
enquiries = Enquiries.objects.all()
serialized_enquiries = EnquirySerializer(enquiries, many=True)
return Response(serialized_enquiries.data, status=status.HTTP_200_OK)
def post(self, request):
created_enquiry = EnquirySerializer(data=request.data)
if created_enquiry.is_valid():
created_enquiry.save()
return Response(created_enquiry.data, status=status.HTTP_201_CREATED)
return Response(created_enquiry.errors)
serializers.py
class EnquirySerializer(serializers.ModelSerializer):
class Meta:
model = Enquiries
fields = '__all__'
In your case of this problem, when the error fires off, it is unable to read the err.response.data property. If there was no error, it would redirect you. In your Django app, check what the error handler is suppose to return.
Quick review of try/catch.
try {
// if everything passes, run this block
} catch (err) {
// if something goes wrong, run this block
}
In this case, be sure to check what your full error is. It might be a 404 or something totally unexpected.

Django throws {"non_field_errors":["Invalid data. Expected a dictionary, but got tuple."]} React JS

Trying to send file via fetch post from react to django rest,but got this django response shows:
{"non_field_errors":["Invalid data. Expected a dictionary, but got tuple."]}
I am trying to send pdf generated file to django but unable to do, first I create pdf then send via fetch post request
ReactJS
genPDF=(evt)=>{
evt.preventDefault();
html2canvas(document.getElementById("pdfid")).then(canvas=>{
let img=canvas.toDataURL('img/png');
let doc=new JsPDF();
doc.addImage(img,'JPEG',30,30);
doc.output('blob');
//doc.save('test.pdf');
let formdata=new FormData();
formdata.append('file',doc);
fetch(`http://127.0.0.1:8000/chauffeur/pdf_upload/`,
{
method: 'POST',
body:formdata,
}
).then(response => response.json()).catch(error => console.error('Error:', error));
//this.postpdf(formdata)
});
};
DJANGO
class PdfUpload(APIView):
parser_classes = (MultiPartParser, FormParser,)
def get(self, request):
return Response([PdfSerializer(file).data for file in Pdf.objects.all()])
def post(self,request):
payload=(request.data,request.FILES)
serializer=PdfSerializer(data=payload)
if serializer.is_valid():
serializer.save()
return Response("File Saved in Backend",status=status.HTTP_201_CREATED)
return Response(serializer.errors,status=status.HTTP_400_BAD_REQUEST)
SERIALIZER.PY
class PdfSerializer(serializers.ModelSerializer):
class Meta:
model = Pdf
fields = ['file',]
Am I send the a correct file because it PDF generated and then afterwards I send it through fetch request or It may be problem in formdata as at the django end it is expecting dictionary but formdata acting as tuple?
Try this:
formdata.append({'file':doc});
Instead of
formdata.append('file',doc);

admin-on-rest Using PATCH method

I am a junior node developer and am trying out admin on rest to quickly run up an admin panel for my json api. However, all of my update requests use patch instead of put. I attempted revising the UPDATE method in my restClient but this seems wrong (the rest of the methods are removed for brevity)
export default (apiUrl, httpClient = fetchJson) => {
const convertRESTRequestToHTTP = (type, resource, params) => {
let url = ''
const options = {}
switch (type) {
case UPDATE:
url = `${apiUrl}/${resource}/${params.id}`
options.method = 'PATCH'
options.body = JSON.stringify(params.data)
break
return { url, options }
}
}
To me this makes sense but when I try to edit an object I get back HTTP/1.1 404 Not Found <pre>Cannot PUT </pre>
I know that that this wasn't possible with previous versions but I read this https://marmelab.com/blog/2017/03/10/admin-on-rest-0-9.html#http-patch but was a little confused on how it works? I guess I just don't know where to start with this.
if problem still is actual now, please check some places which are using by me to set my customRestClient.
// App.js
import customRestClient from './customRestClient';
in my case i'm using httpClient to add custom headers:
import httpClient from './httpClient';
below:
const restClient = customRestClient('my_api_url', httpClient);
and finally:
<Admin title="Admin Panel" restClient={restClient}>

Resources