I'm trying to send a POST request to my Django view using plain javascript (I don't want any unnecessary libraries involved). The data is not sent through a form, but by using fetch. For now I just want to be able to manipulate the request.POSTin my views.py, nothing more.
Here's my code:
Javascript
let article = document.querySelector('article')
articleId = article.getAttribute('data-product-id')
# some other stuff
fetch("{% url 'shop:shoplist' 1 %}", {
method: 'POST',
dataType: "application/json",
data: {'article_id': articleId},
headers: {'X-CSRFToken': csrf_token}
})
Python
if request.method == 'POST':
testing = request.POST
return JsonResponse({'test': testing})
The request is sent, the csrftoken is received correctly, but the request.POST returns just <QueryDict: {}>, instead of what I'm expecting (headers, data...).
I've searched and found a lot of similar questions, the most similar one being this one, but still I can't seem to find a solution.
Any idea?
Try to add 'content-type' to headers in fetch call (instead of dataType parameter) and change data parameter to body with stringified object:
fetch("{% url 'shop:shoplist' 1 %}", {
method: 'POST',
body: JSON.stringify({'article_id': articleId}),
headers: {
'X-CSRFToken': csrf_token,
'Content-Type': 'application/json'
}})
The request.POST contains only the parameters that are form encoded. Since your data type is application/json they are empty. The request.body contains the actual json data.
Related
I have some json data that I have been posting to an API using $.ajax but I would like to update this to use the fetch API. However I seem to have it setup the Fetch API request ends up returning a 403 so I must be missing something but I can't work it out.
Ajax request:
$.ajax({
type: 'POST',
url: url,
data: {
'title': data.title,
'body': data.body,
'csrfmiddlewaretoken': csrf_token,
'request_json': true
},
success: function (data) {
console.log(data)
}
});
Fetch attempt (one of many):
let payload = {
'title': data.title,
'body': data.body,
'csrfmiddlewaretoken': csrf_token,
'request_json': true
}
let request = new Request(url, {
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: JSON.stringify( payload )
});
fetch(request)
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
})
.then((response) => response.json())
I have tried with various different headers, content encoding and sending the data as form data using:
let form_data = new FormData();
form_data.append( "json", JSON.stringify( payload ) );
let request = new Request(url, {
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: form_data
});
...
Any help would be great and if you need any more info let me know
Thanks
To port an existing jQuery.ajax request to fetch, you need to consider that jQuery always includes cookies for you, but fetch does not.
Quoting MDN (emphasis mine):
Note that the fetch specification differs from jQuery.ajax() in mainly two ways that bear keeping in mind:
- The Promise returned from fetch() won’t reject on HTTP error status [ ... ]
- By default, fetch won't send or receive any cookies from the server, resulting in unauthenticated requests if the site relies on maintaining a user session (to send cookies, the credentials header must be sent).
Edit: spec has changed since then, so this should no longer be a problem:
Since Aug 25, 2017. The spec changed the default credentials policy to same-origin. Firefox changed since 61.0b13.
So the following (returning to original answer) only applies to "older" browsers.
Thanks David Richmond from comments :)
So you get 403 (Forbidden) because your API likely relies on cookies for authentication/authorization (even in your case, where you send a csrfmiddlewaretoken, the server-side framework might still expect a cookie with that -- guessing Django?).
To fix this, add credentials: "same-origin" to your Request (*), like so:
let request = new Request(url, {
method: 'post',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
(*) Valid options for credentials are:
omit: Never send cookies. This is the default (and your problem).
same-origin: Only send cookies if the URL is on the same origin as the calling script.
include: Always send cookies, even for cross-origin calls.
You say:
'Content-Type': 'application/x-www-form-urlencoded'
and
body: JSON.stringify( payload )
JSON Encoding is not the same thing as WWW Form Encoding!
You also tried
form_data.append( "json", JSON.stringify( payload ) );
FormData objects are converted to Multipart MIME.
Multipart MIME is also not the same as WWW Form Encoded data.
JSON nested inside Multipart MIME even less so.
This question describes how to convert an object (payload) into a Form Encoded string.
My problem
My problem is that when sending a json document via post to a rest framework api, the document converts a key and adds an empty value.
file.js
function SaveFiles(task_id) {
data = {'taskid': task_id,'file_list': file_list};
$.ajax({
url: '/tarjet/save.../',
type: 'POST',
data: JSON.stringify(data),
async:false,
success:function(data){
},
complete:function(){},
error:function (xhr, textStatus, thrownError){
}
});
}
api.py
class SaveTaskFiles(APIView):
authentication_classes = (TokenAuthentication,)
def post(self, request):
a = request.DATA
javascript output
{"taskid":"1198792","file_list":[{"id":0,"file_name":"image2.png","upload_date":"11/16/2016","file_description":"","download_link":"a6175ab4-ac58-11e6-8e10-00dbdfd54837.png","isdeactivate":"0","sft_f":"svt"}]}
actual input request.DATA
{u'{"taskid":"1198792","file_list":[{"id":0,"file_name":"image2.png","upload_date":"11/16/2016","file_description":"","download_link":"a6175ab4-ac58-11e6-8e10-00dbdfd54837.png","isdeactivate":"0","sft_f":"svt"}]}': [u'']}
Is a Querydict type.
I can not explain why my json file becomes a key,and this value is a empty list, i have solved it using request.body, but I know it's not the convention. I've tried dumps, loads, but it did not work, although I could get the key and use it, I do not think that's a good idea.
Beforehand thank you very much ¡
You need to send the correct content-type header to tell DRF that you are sending a JSON document, not a plain string.
$.ajax({
contentType: 'application/json',
...
});
See the DRF parsers docs.
Team,
I'm trying to consume a rest endpoint which returns a json response using ajax. To my luck, the rest end point is returning the response which I could track it via Network section of Google chrome.
I tried many alternatives such as,
Changed datatype from json to jsonp.
Allowed CORS on the rest point.
Still the code is not working. I have added console.log in .done function of ajax to print the response on the console and nothing is getting printed on the console.
function getJson(){
var json = $.ajax({
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
url: 'http://charlie-halls-91456.herokuapp.com/retrieve/date/2011-06-13/2012-06-14',
type: "get",
dataType: 'jsonp',
}).done(function(response){
alert("Response is " +response);
});
}
I am eager to use the new fetch method and I understand it can be used for all http method calls to a Rest endpoint.
So I have the following very simple API endpoint which I know works, get and post requests work. The issue I am having is getting the post request to work on the fetch method.
My code for the fetch method is
var ingredient = {
'name': 'Salt',
}; // a JSON object used as the body for the post request
fetch('http://pantler-180711.nitrousapp.com/api/v1/Ingredients',
{
method: "POST",
body: JSON.stringify( ingredient )
})
.then(function(res){ console.log( 'Data posted') })
I then get the following error message. 422 (Unprocessable Entity)
If on the other hand I do something very similar but this time using the classic jQuery ajax post method it works.
$.ajax({
url: 'http://pantler-180711.nitrousapp.com/api/v1/Ingredients',
type: 'POST',
data: 'name=Salt', // or $('#myform').serializeArray()
success: function() { console.log('Data posted'); }
});
Any help here would be appreciated, it feels like I am missing something small here, and documentation on fetch method is scant on the web.
The two request sends two different post bodies one is application/x-www-form-urlencoded (jQuery) and the other is application/json.
You'll have to change the fetch call to send the same type of data as the $.ajax call.
You may have to explicitly set the content type in the fetch request.
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
var searchParams = new URLSearchParams();
searchParams.append("name", "Salt");
fetch('http://pantler-180711.nitrousapp.com/api/v1/Ingredients',
{
method: "POST",
headers: myHeaders,
body: searchParams
})
.then(function(res){ console.log( 'Data posted') })
As the title suggests, I am attempting to write an AJAX call using Angular's $http object. I would like to send a post request that submits JSON in the body of the request, but also has a query string appended to the URL. After reading the documentation for $http all the way through, surfing for answers on SO, and attempting several different configurations, I am still not clear on how to pass the parameters correctly. Currently, my API responds with either empty JSON or Bad Request (400).
Code
function inviteStaff (json) {
authToken = service.getAuthToken();
return $http({
method: 'POST',
url: baseUrl + '/invitations' + '?authToken=' + authToken,
data: JSON.stringify(json),
headers: {
'Content-type': 'application/json'
}
});
}
Consuming Function
self.invite = function ($form) {
self.showLoader = true;
InvitesService.inviteStaff($scope.inviteModel).then(function () {
$form.$setPristine();
$scope.inviteModel.timestamp = new Date();
$scope.invites.unshift($scope.inviteModel);
$scope.inviteModel = self.createNewInvite();
self.showLoader = false;
},
function () {
self.showLoader = false;
});
};
Request log
data: "authToken=********&t=*****" // this is weird because it adds another "t" parameter to this query string here
headers: Object
Accept: "application/json"
Accept-Language: "en"
Content-type: "application/json"
authToken: "*********" // just the token here
__proto__: Object
method: "POST"
params: Object
timeout: Object
transformRequest: Array[1]
transformResponse: Array[1]
url: "http://****.****.com:****/doctors/invitations?authToken=*****"
__proto__: Object
Param JSON object
{
accountType: "LEAD_DOCTOR",
firstName: "kraken",
lastName: "lastName",
email: "kraken#mail.com"
}
Can anyone shed a little light on how this should work? Do I pass the json object to data or params? Do I need to use JSON.stringify on the data field? In some cases, I also saw people passing an empty string to data.
I also have been running it through Charles Proxy. I can get very close to what I want, but for some reason the JSON is showing under the JSON Text filter as a query string, and not the JSON filter.
Any ideas would be helpful. I will be refactoring and upgrading it all to use the $resource tool, but we are on a tight deadline and I just want to get this working for now. Let me know if you need more info.
UPDATE
We're using swagger to document our API, and you can actually hit the endpoint in their UI. It produces the following CURL command. I thought it might help spot the issue.
curl -X POST --header "Content-Type: application/json" --header "Accept: application/json" -d "{
\"firstName\": \"string\",
\"lastName\": \"string\",
\"email\": \"blah#mail.com\",
\"accountType\": \"CLIENT\"
}" "http://*****.****.com:****/doctors/invitations?authToken=***obfuscatedAuthToken***"
I would recommend you to take advantage of the header field:
function inviteStaff (json) {
authToken = service.getAuthToken();
return $http({
method: 'POST',
url: baseUrl + '/invitations',
data: JSON.stringify(json),
headers: {
'Content-type': 'application/json',
'authToken': authToken
}
});
}
Then on the server side, ask the current request for the header value of authToken.
Passing Tokens in the URL has some security concerns, for your reading: https URL with token parameter : how secure is it?
It turns out we had an HTTP Interceptor function elsewhere in the codebase that was changing the content-type! Wrapped that in a conditional, and it is good to go now. Thanks to everyone who helped me ensure my code was written correctly!