I want to pass JSON object and image at the same time from react to my django rest api.
I've tried some solutions from this forum and tutorials but nothing worked in my case. Previously I have been using JSON.stringify on state fields and that worked but i couldn't pass image and now i ended up on something like this.
postData.js
let formData = new FormData();
const { title, description, image, ingredient, step } = this.state;
formData.append('image', image);
formData.append('title', title);
formData.append('description', description);
formData.append('ingredient', ingredient);
formData.append('step', step);
let conf = {
method: 'post',
body: formData,
headers: new Headers({
'Content-Type': 'multipart/form-data',
'Accept': 'application/json'
})
};
fetch('http://127.0.0.1:8000/api/', conf)
.then(res => {
this.props.history.push('/');
})
.catch(err=>{console.log(err)})
serializers.py
class RecipeSerializer(serializers.ModelSerializer):
ingredient = IngredientSerializer(many=True, required=False)
step = StepSerializer(many=True, required=False)
class Meta:
model=models.Recipe
fields=('id', 'title', 'description', 'image', 'ingredient', 'step', )
def create(self, validated_data):
ingredient_data = validated_data.pop('ingredient')
step_data = validated_data.pop('step')
recipe = models.Recipe.objects.create(**validated_data)
for ingredient in ingredient_data:
models.Ingredient.objects.create(recipe=recipe, **ingredient)
for step in step_data:
models.Step.objects.create(recipe=recipe, **step)
return recipe
views.py
class ListRecipes(generics.ListCreateAPIView):
queryset = Recipe.objects.all()
serializer_class = RecipeSerializer
class DetailRecipe(generics.RetrieveUpdateDestroyAPIView):
queryset = Recipe.objects.all()
serializer_class = RecipeSerializer
I always get POST http://127.0.0.1:8000/api/ 400 (Bad Request) error.
--EDIT--
I changed the code and created new endpoint for uploading images and technically it works but I'm trying to append image and created recipe id to FormData() and when I log it to the console it contains both of them but when I'm printing it on the backend it contains only image.
postData.js
formData.append('id', this.state.id);
formData.append('image', image, image.name);
for(let key of formData.entries()){
console.log(key);
}
let data = {
method: 'post',
body: formData,
headers: new Headers({
'Accept': 'application/json'
})
};
fetch('http://127.0.0.1:8000/api/images/', data)
.then(res => {
console.log(res);
})
.catch(err=>{
console.log(err);
})
serializers.py
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = models.Image
fields = ('id', 'image', )
def create(self, validated_data):
print(validated_data)
recipe = models.Recipe.objects.get(id=validated_data.id)
image = models.Image.objects.create(recipe=recipe, **validated_data)
return image
You should add the DRF MultiPartParser to your views' parser_classes
Related
I am trying to send some data from the client side (react native) that includes a few images so I append them to formdata and successfully send it through a post request but I am having trouble figuring out how to handle it on the server side.
My react code:
const post = async () => {
const token = await getToken();
const [description, setDescription] = useState('');
const formData = new FormData();
images.forEach((image) => {
formData.append(`images`, {
uri: image,
type: 'image/jpeg',
name: image,
});
});
formData.append('description', description);
console.log('formdata:', formData);
try {
await axios.post(URL, formData._parts, {
headers: {
'Content-Type': 'multipart/form-data',
Authorization: token,
},
});
} catch (e) {
console.log(e);
}
};
when i console log formData._parts on client side i get:
formdata: [["images", {"name": "/Library/Developer/CoreSimulator/Devices/123.jpg", "type": "image/jpeg", "uri": "/Library/Developer/CoreSimulator/Devices/123.jpg"}], ["images", {"name": "/Library/Developer/CoreSimulator/Devices/456.jpg", "type": "image/jpeg", "uri": "/Library/Developer/CoreSimulator/Devices/456.jpg"}], ["description", "Test"]]
It post request only works whenn i send formData._parts but not when i send just formData
on my server side (django/drf):
models.py:
class Post(models.Model):
user_id = models.ForeignKey(
User, on_delete=models.CASCADE, default=None
)
images = models.FileField(
max_length=3000, default=None, null=True, blank=True, upload_to='media/post_images')
description = models.TextField(null=False, default=None)
date = models.DateTimeField(editable=False, auto_now_add=True)
serializers.py:
class PostSerializer(serializers.ModelSerializer):
images = serializers.FileField()
class Meta:
model = Post
fields = "__all__"
views.py
class PostView(APIView):
serializer_class = PostSerializer
parser_classes = (MultiPartParser, FormParser)
def post(self, request, format=None):
form_data = request.data
images = form_data.get('images')
description = form_data.get('description')
user_id = self.request.user
print(form_data)
post = Post.objects.create(
images=images, description=description, user_id=user_id)
post.save()
serializer = PostSerializer(post)
return Response(serializer.data, status=status.HTTP_201_CREATED)
when i print the form_data in python i get:
<QueryDict: {'0': ['images'], '1.uri': ['/Library/Developer/CoreSimulator/Devices/123.jpg'], '1.type': ['image/jpeg'], '1.name': ['/Library/Developer/CoreSimulator/Devices/123.jpg'], '1.0': ['images'], '1.1.uri': ['/Library/Developer/CoreSimulator/Devices/456.jpg'], '1.1.type': ['image/jpeg'], '1.1.name': ['/Library/Developer/CoreSimulator/Devices/456.jpg'], '2.0': ['description'], '2.1': ['Test']}>
How can i extract the data and save it to the database?
Django is handling that for you. You can try to access your images with:
request.FILES.getlist("images")
This will give you a list of all the images that are found in the submitted form.
EDIT:
For the Backend actually being able to read the data, it obviously also has to be send. To append the data you can use something like this:
var formData = new FormData();
formData.append('avatar', {uri: this.state.avatar.uri, name: 'yourname.jpg', type: 'image/jpg'});
let response = await fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data',
'Authorization': ' Token '+accessToken,
},
body: formData
});
Using raw HTML when I post a file to a flask server using the following I can access files from the flask request global:
<form id="uploadForm" action='upload_file' role="form" method="post" enctype=multipart/form-data>
<input type="file" id="file" name="file">
<input type=submit value=Upload>
</form>
In flask:
def post(self):
if 'file' in request.files:
....
When I try to do the same with Axios the flask request global is empty:
<form id="uploadForm" enctype="multipart/form-data" v-on:change="uploadFile">
<input type="file" id="file" name="file">
</form>
uploadFile: function (event) {
const file = event.target.files[0]
axios.post('upload_file', file, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
If I use the same uploadFile function above but remove the headers json from the axios.post method I get in the form key of my flask request object a csv list of string values (file is a .csv).
How can I get a file object sent via axios?
Add the file to a formData object, and set the Content-Type header to multipart/form-data.
var formData = new FormData();
var imagefile = document.querySelector('#file');
formData.append("image", imagefile.files[0]);
axios.post('upload_file', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
Sample application using Vue. Requires a backend server running on localhost to process the request:
var app = new Vue({
el: "#app",
data: {
file: ''
},
methods: {
submitFile() {
let formData = new FormData();
formData.append('file', this.file);
console.log('>> formData >> ', formData);
// You should have a server side REST API
axios.post('http://localhost:8080/restapi/fileupload',
formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function () {
console.log('SUCCESS!!');
})
.catch(function () {
console.log('FAILURE!!');
});
},
handleFileUpload() {
this.file = this.$refs.file.files[0];
console.log('>>>> 1st element in files array >>>> ', this.file);
}
}
});
https://codepen.io/pmarimuthu/pen/MqqaOE
If you don't want to use a FormData object (e.g. your API takes specific content-type signatures and multipart/formdata isn't one of them) then you can do this instead:
uploadFile: function (event) {
const file = event.target.files[0]
axios.post('upload_file', file, {
headers: {
'Content-Type': file.type
}
})
}
Sharing my experience with React & HTML input
Define input field
<input type="file" onChange={onChange} accept ="image/*"/>
Define onChange listener
const onChange = (e) => {
let url = "https://<server-url>/api/upload";
let file = e.target.files[0];
uploadFile(url, file);
};
const uploadFile = (url, file) => {
let formData = new FormData();
formData.append("file", file);
axios.post(url, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
}).then((response) => {
fnSuccess(response);
}).catch((error) => {
fnFail(error);
});
};
const fnSuccess = (response) => {
//Add success handling
};
const fnFail = (error) => {
//Add failed handling
};
This works for me, I hope helps to someone.
var frm = $('#frm');
let formData = new FormData(frm[0]);
axios.post('your-url', formData)
.then(res => {
console.log({res});
}).catch(err => {
console.error({err});
});
this is my way:
var formData = new FormData(formElement);
// formData.append("image", imgFile.files[0]);
const res = await axios.post(
"link-handle",
formData,
{
headers: {
"Content-Type": "multipart/form-data",
},
}
);
How to post file using an object in memory (like a JSON object):
import axios from 'axios';
import * as FormData from 'form-data'
async function sendData(jsonData){
// const payload = JSON.stringify({ hello: 'world'});
const payload = JSON.stringify(jsonData);
const bufferObject = Buffer.from(payload, 'utf-8');
const file = new FormData();
file.append('upload_file', bufferObject, "b.json");
const response = await axios.post(
lovelyURL,
file,
headers: file.getHeaders()
).toPromise();
console.log(response?.data);
}
There is an issue with Axios version 0.25.0 > to 0.27.2 where FormData object in a PUT request is not handled correctly if you have appended more than one field but is fine with one field containing a file, POST works fine.
Also Axios 0.25.0+ automatically sets the correct headers so there is no need to specify Content-Type.
For me the error was the actual parameter name in my controller... Took me a while to figure out, perhaps it will help someone. Im using Next.js / .Net 6
Client:
export const test = async (event: any) => {
const token = useAuthStore.getState().token;
console.log(event + 'the event')
if (token) {
const formData = new FormData();
formData.append("img", event);
const res = await axios.post(baseUrl + '/products/uploadproductimage', formData, {
headers: {
'Authorization': `bearer ${token}`
}
})
return res
}
return null
}
Server:
[HttpPost("uploadproductimage")]
public async Task<ActionResult> UploadProductImage([FromForm] IFormFile image)
{
return Ok();
}
Error here because server is expecting param "image" and not "img:
formData.append("img", event);
public async Task<ActionResult> UploadProductImage([FromForm] IFormFile image)
I'm sending video as POST request using Axios with 'Content-Type': 'multipart/form-data', but inside Spring I see the following error:
"[org.springframework.web.HttpMediaTypeNotSupportedException: Content type '' not supported]"
The Axios code :
async function uploadPost() {
const type = 'video/mp4';
let data = new FormData();
const videoUri = imagePreview;
data =
('video',
{
name: 'mobile-video-upload',
type,
videoUri,
});
axios({
method: 'post',
url: 'http://localhost:8080/post/upload',
body: data,
headers: {
'Content-Type': 'multipart/form-data',
},
}).catch(function(error) {
console.log(error);
});
The function inside Spring :
#CrossOrigin
#ResponseBody
#RequestMapping(value = "/post/upload", method = RequestMethod.POST, consumes = "multipart/form-data")
public String create(#RequestParam("file") MultipartFile file) {
Thanks in advance.
Assuming Spring code to be correct, please try with minor changes with Axios.
const axios = require('axios')
const qs = require('querystring')
....... other code .....
const config = {
headers: {
'Content-Type': 'multipart/form-data'
}
}
axios.post(url, qs.stringify(requestBody), config)
.then((result) => {
// Do somthing
})
.catch((err) => {
// Do somthing
})
I have the following code for creating an event with a image and some body params. It was working fine when i was doing it without image, i am using react-native-image-crop-picker for selecting images. I am getting "Network request failed" error when posting data from react-native. The request never reach my backend as i am getting no logs there. It is working fine with postmen.
MY CODE:
const { name, date, description, location, uri, mime, time } = this.state;
const formData = new FormData();
formData.append('name', name)
formData.append('date', date)
formData.append('description', description)
formData.append('location', location)
formData.append('time', time)
formData.append('image',{
uri:uri,
mime:'image/jpeg',
name:`image${moment()}`
})
alert(JSON.stringify(formData));
const config = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data',
},
body: formData,
};
fetch(`http://${Config.apihost}:${Config.port}/events`,config).then((res) => res.json())
.then((res) => {
this.setState({ modalVisible: false, name:'', date: moment().format('YYYY-MM-DD'), description:'', Location: 'AlHedaya Masjid' })
this.props.addEvent(res.message);
// this.props.navigation.goBack();
}).catch((err) => alert(err));
I have another screen which contains different number of pictures like gallery i am uploading multiple picture to the gallery, the request is working fine with code below.
const data = new FormData();
data.append('name', 'avatar');
images.map((res, i) => {
data.append('fileData[]', {
uri: res.path,
type: res.mime,
name: `image${i}${moment()}`
});
})
const config = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data',
},
body: data,
};
fetch(`http://${Config.apihost}:${Config.port}/events/${this.state.item.id}/photos`, config)
.then((checkStatusAndGetJSONResponse) => checkStatusAndGetJSONResponse.json())
.then((response) => {
if (response.status && response.message.length > 0) {
var images = this.state.images;
response.message.map(file => {
images.push(`http:${Config.apihost}:${Config.port}/images/${file.id}`);
});
this.setState({ images });
}
}).catch((err) => { alert(err) });
I can't really see the difference between the two codes but the upper code giving me error.
I am testing on android
I am using the IP address instead of localhost (my others requests are working so thats out of equation)
None of the solution in this link worked
React Native fetch() Network Request Failed
Am I missing something?
In first code snippet you have written mime instead of type.
formData.append('image',{
uri:uri,
**mime:'image/jpeg**',
name:`image${moment()}`
})
it should be like below snippet
formData.append('image',{
uri:uri,
type:'image/jpeg',
name:`image${moment()}`
})
According the Axios, this should be possible:
https://github.com/axios/axios/issues/462#issuecomment-252075124
I have the following and pos_title does have a value.
export function getQuery(pos_code, id) {
if (id === 94) {
var strArray = pos_code.split(' - ');
pos_code = strArray[0];
var pos_title = strArray[1];
}
return function(dispatch) {
axios.get(
`${URL}/api/survey/${(id)}/results/${(pos_code)}/`,
{
headers: {
'Content-Type': 'application/json',
'Authorization': 'JWT ' + sessionStorage.getItem('token')
},
data: {
body: pos_title
}
}
)
.then(response => {
dispatch({
type: QUERY,
payload: response.data
})
})
.catch(error => {
console.log(error);
})
}
}
In the corresponding views.py, the print(body_data) is empty:
class GetQueryDataAPIView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
data = {'id': request.user.id}
if kwargs:
data['survey_id'] = kwargs.get('survey_id')
data['pos_code'] = kwargs.get('pos_code')
if data['survey_id'] == 94:
body_unicode = request.body.decode('utf-8')
body_data = json.loads(body_unicode)
print(body_data)
serializer = GetQueryDataSerializer(data=data)
if serializer.is_valid(raise_exception=True):
return Response(serializer.data, status=HTTP_200_OK)
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
As Keith Brewster, Axios uses XMLHttpRequest which does not support sending data in the body of a request. One solution would be to do what David Ryan suggested and add pos_title to part of the URL. This creates some headaches though if there are spaces in the pos_title which there are in my case.
However, in my case, I decided to do filtering on the client-side, so keeping things as they were, and filtering the response was sufficient to resolve my issue.
If it's possible to modify your API URL, add pos_title as a query parameter.
This will get around any issues you may have in relation to sending a request body in a GET request. If you must send a request body, it sounds like you should be using a PUT request.
For anyone coming to the same question I am posting the reply using your code:
You have to provide params with the get request like.
export function getQuery(pos_code, id) {
if (id === 94) {
var strArray = pos_code.split(' - ');
pos_code = strArray[0];
var pos_title = strArray[1];
}
return function(dispatch) {
axios.get(
`${URL}/api/survey/${(id)}/results/${(pos_code)}/`,
{
headers: {
'Content-Type': 'application/json',
'Authorization': 'JWT ' + sessionStorage.getItem('token')
},
params: {
'title': pos_title
}
}
)
}
}
And in the django part one can get the query_params as below:
def get(self, request, *args, **kwargs):
title = request.query_params['pos_title']