AXIOS POST request + render_template FLASK - javascript

I have a page with a ton of input boxes (numbers, checkboxes etc.), I need to prepare the data before the POST request. So I have a bunch of methods that make adjustments to the names of these inputs, bundle them up nicely in one object called: data. For my purposes, putting the submit button in the above form does not work, because the POST request is done with the default names. For that reason all the above inputs are enclosed in a div, and then I have a submit button and onClick I am doing an axios POST request:
axios({
method: 'post',
url: '/smart-beta/',
data
});
On the Flask end I have this:
elif request.method == "POST":
sbe_args = json.loads(request.data)
sb = SbeGenerator(sbe_args)
sb.run() # TODO: depending on how long this takes, may add a loading animation to the page
eg = ExcelGenerator(sb)
eg.create_excel()
beta = sb.beta
sharpe = sb.sharpe
annualised_return = sb.ann_mean
annualised_vol = sb.ann_vol
time_series = sb.port_json
stocks = sb.last_stocks_json
print("Time series: " + str(time_series))
print("stocks: " + str(stocks))
# TODO: Do the redirect here (getting 302 status on the POST request if redirect is here, becasue
# TODO: axios catches it in then
print("Rendering template")
return render_template('smart_beta/calculation.html')
I get the 200 on the POST request, but it never renders my other page. It just hangs on the current page. Is that because the axios's promise is never resolved? when I do .then(function(response) {console.log(response);}); <- it still hangs. The reason I need to use render_template is because I need to pass stuff like time_series into that page.
EDIT: When I navigate to the response of the POST request in the dev tools on the web page, I can see that it is the page that I need.

Related

AJAX not changing/redirecting from login page to dashboard [flask backend]

I am using the post method for the login. My ajax function sends the data successfully to my flask backend server [I know because it returns a response to my ajax]. Supposedly, after receiving the respnse from the backend, my ajax success handler will navigate/redirect to the dashboard page but IT DOES NOT! How do I make it navigate/redirect to another page/url?It returns a 200 status code so I do not know why it does not display the dashboard page.
WHAT I HAVE TRIED:
I have tried using window.location.href, window.location.replace but to no avail, still it does not work. I have also tried changing the method to GET but its still the same. I have also set async to false because ajax would not post if I would not set it to false.
AJAX
$.ajax({
type: "POST",
url: 'http://127.0.0.1:5000/processlogin',
data: JSON.stringify(loginobject),
contentType: "application/json;charset=utf-8",
async: false,
success: function (resp) {
window.location.href = ("http://127.0.0.1:5000/dashboard");
},//success
failure: function (resp) {
alert(resp.message);
}
});
backend flask functions
This functions work 100%. Already tested it with POSTMAN. I have also queried the database using my stored procedure and it does well.
This displays the login form
#app.route('/', methods=['GET','POST'])
def login():
return render_template('login.html')
This processes the ajax's sent data. In short this is the function ajax is communicating with
#app.route('/processlogin', methods=['POST'])
def processlogin():
loginobject = request.get_json(force=True)
username = loginobject['username']
password = loginobject['password']
try:
dbpassword = callstoredproc("getpassword", (username,))[0][0]
if dbpassword == 'null':
return jsonify({'status':'error', 'message':'Username does not exist!'})
elif bcrypt.verify(password, dbpassword) == True:
return jsonify({'status':'ok'})
except Exception as e:
print(e)
And this is what I am trying to display: the dashboard html
#app.route('/dashboard', methods=['GET', 'POST'])
def dashboard():
return render_template('dashboard.html')
Remove the curved brackets and try again:
window.location.href = "http://127.0.0.1:5000/dashboard";
It works also with curved brackets so just be sure that your response arrive correctly to the success callback.
See also best answer on SO.
It should also be error instead of failure as error callback.
error: function (resp) {
alert(resp.message);
}
jsfiddle Example

Flask Session Not Persisting (Postman works, Javascript doesn't)

I'm developing a Flask server to communicate between some backend Python functionality and Javascript clients over the web. I'm attempting to utilize Flask's session variable to store user specific data over the course of their time interacting with the app. I've removed most of the application specific code below but the core problem I'm experiencing remains.
Here is my the code for my (simplified) Flask app:
import json
import os
from flask import Flask, jsonify, request, session
app = Flask(__name__)
app.secret_key = 'my_secret_key'
#app.route('/', methods=['GET'])
def run():
session['hello'] = 'world'
return jsonify(session['hello'])
#app.route('/update', methods=['POST'])
def update():
return jsonify(session['hello'])
if __name__ == '__main__':
app.run(host='0.0.0.0')
Utilizing Postman, I can make a GET request to my server and receive the expected output of "world". I can then make a POST request with an arbitrary body and receive the same expected output of "world" (again using Postman).
When using Chrome, I can visit my server IP and see the expected output "world" on the page. I can also manually make a GET request using Javascript (in Chrome's console) and receive the same response as expected. However, my problem arises when trying to send a POST request to the server using Javascript; the server shows a KeyError: 'hello' when trying to make this request.
Here is the Javascript I'm using to make the POST request:
var url = 'http://my_server_ip/update';
fetch(url, {
method: 'POST',
body: JSON.stringify('arbitrary_string'),
headers: new Headers({
'Content-Type': 'application/json'
})
})
.then(response => response.json())
.then((data) => {
console.log(data);
})
What's going wrong here? Why can I make the GET/POST requests with Postman just fine but run into errors making the same requests with Javascript?
The caveats section of the fetch documentation says:
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.
It is recommended to use AJAX to exchange information with Flask views.
Meanwhile, in your code for the Flask app, the session object is a dictionary. Now, if you access a dictionary with its key session['hello'] and if this key does not exist, a Keyerror is raised. To get around this error, you can use the get() method for dictionaries.
What is happening is: the fetch request does not find the hello key(or GET the session value from the Flask view) in the Flask session.
user = session.get('hello')
return jsonify(session_info=user)
But this will still give you a null value for the session { session_info: null }. Why is that so?
When you send GET/POST requests to the Flask server, the session is initialized and queried from within Flask. However, when you send a Javascript fetch POST request, you must first GET the session value from Flask and then send it as a POST request to your Flask view which returns the session information.
In your code, when the POST request is triggered from fetch, when I send the payload data to Flask, it is received correctly and you check this using request.get_json() in the Flask view:
#app.route('/update', methods=['POST'])
def update():
user = session.get('hello')
payload = request.get_json()
return jsonify(session_info=user, payload=payload)
This will return { payload: 'arbitrary_string', session_info: null }. This also shows that fetch does not receive the session information because we did not call GET first to get the session information from Flask.
Remember: The Flask session lives on the Flask server. To send/receive information through Javascript you must make individual calls unless there is a provision to store session cookies.
const fetch = require('node-fetch');
var url_get = 'http://my_server_ip';
var url_post = 'http://my_server_ip/update';
fetch(url_get, {
method:'GET'
}).then((response)=>response.json()).then((data) =>fetch(url_post, {
method: 'POST',
body: JSON.stringify(data),
dataType:'json',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then((postdata) => {
console.log(postdata);
}));
The Flask views will change slightly:
#app.route('/', methods=['GET'])
def set_session():
session['hello'] = 'world'
return jsonify(session['hello'])
#app.route('/update', methods=['POST'])
def update():
payload = request.get_json()
return jsonify(session_info=payload)
When you trigger the Javacript request now, the output will be: { session_info: 'world' }
After a few hours of testing, I managed to figure out the issue. Although I think #amanb's answer highlights the problem, I'm going to answer my own question because what I found is ultimately a simpler solution.
In order to make the POST request return the expected value, I simply needed to add a credentials: 'same-origin' line to the fetch body. This looks like the following:
var url = 'http://my_server_ip/update';
fetch(url, {
method: 'POST',
body: JSON.stringify('arbitrary_string'),
credentials: 'same-origin', // this line has been added
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then((data) => {
console.log(data);
})
According to Mozilla's Fetch usage guide,
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.
So it seems I looked over this. Changing the credentials to allow communication of the cookie/session between client and server resolved the issue.

In Javascript how can I redirect an Ajax response in window location

I currently have the following working piece of code (angular but applies to any JS framework):
var url = '/endpoint/to/my/file';
$http({
method: 'GET',
url: url
})
.success(function(jdata) {
window.location = url;
})
.error(function(je){
// display errors on page
});
The above is called after a form was completed and the user has clicked on "submit" (the real situation is a bit more complex than this but it is the same idea). I do the form check asynchronously, so there's no page reload.
If the request is successful, returns a binary (a pdf file), if not succesful, the request returns a 400 BadRequest with errors formatted in JS. So what I do is, if successful, I redirect to the same url to have the PDF otherwise I get the JSON error object and do something with it.
How can I refrain from making two requests if the requests is successful?
Note1: on the backend side I would like to keep only one route that does everything, check + return PDF
Note2: the current situation is pretty neat in my opinion, since I have an asynchronous form check and if successful the file downloads directly in the browser since I have "CONTENT-DISPOSITION" -> "attachment" in the HTTP header of the successful response
Update: additional information about the architecture as requested by Emile:
In my use case I have one endpoint that checks inputs (and other external requirements). For security reasons I cannot output the PDF if all requirements are not satisfied so I have to do the check prior to delivering the file ( the file is automatically generated) anyway. So having two endpoints would just be redundant and add some unnecessary complexity.
While writing I think an alternative solution could be to pass an argument on the endpoint while doing the check, so that if successful, it stops and does not generate the PDF, and then redirect to the same endpoint without the flag which will output the PDF.
So I do the check twice but only load (and generate - which is resource intensive) the file only once and I have only one endpoint...
Here's the adapted code:
var url = '/endpoint/to/my/file';
$http({
method: 'GET',
url: url+'?check'
})
.success(function(jdata) {
window.location = url;
})
.error(function(je){
// display errors on page
});
On the backend side (I use Play framework/Scala)
def myendpoint(onlyDoCheck: Boolean = false) = Action{implicit request =>
myForm.bindFromRequest.fold(
e => BadRequest(myErrors),
v => if(onlyDoCheck) Ok(simpleOkResponse) else Ok(doComputationgeneratefileandoutputfile)
)
}
The real deal
The best you could do is split your endpoint.
One for the form and the convenience of having errors without refresh.
Then, on success, redirect to your other endpoint which only downloads the file.
If the file was on the disk and wasn't auto-generated and required to be authenticated to be downloaded, you could hide the file behind a normal endpoint, do the checks, and return the file using X-Accel-Redirect header with nginx or X-Sendfile using apache.
The hack
Disclaimer: This is more of a hack than the best solution. As mention by #Iceman, Safari, IE, Safari-iOS, Opera-mini and some such browsers don't support this particular spec.
In your server-side endpoint, if the file is available without errors, you can set the header to the content-type of the file (like 'application/pdf') so the download will starts automatically.
If there are errors, don't set the header and return a json of the errors to inform the user through javascript.
Since we don't know what's behind, here's a python (Django) example:
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename=your_filename.pdf'
response.write(repport.printReport(participantId))
return response
You can handle the response in the ajax success callback:
$.ajax({
url: 'endpoint.php',
success: function(data) {
var blob = new Blob([data]);
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "filename.pdf";
link.click();
}
});
You could also try the jQuery fileDownload plugin mentioned in this answer.

Can not post data via ajax to web2py rest api - possible CORS issue

I've searched for answers to this issue for nearly two weeks. I have a simple system built in web2py. Note: I'm not exactly a python veteran. I am attempting to use the web2py rest api to post data to the database. If I run the curl command, the database table is updated and the rest returns the id of the newly added row. This is the desired outcome. However, if I attempt to use an ajax request to perform the same action, the request runs successful but the rest returns an empty object and the database is not updated. I've added a CORS wrapper class which allows me to get past the cross-origin issue; but I'm not sure if this is at the same time preventing the database from updating etc. I'm stomped. Also note that I formatted the data (in the ajax call) as a json object as well, but still nothing. Please find all the code below.
MOST IMPORTANT: I am receiving the following message - Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Any/All help is greatly appreciated guys. Thanks :)
#Web2py model
db.define_table(‘myboard',
#Field('userid','reference auth_user'),
Field('userid',db.auth_user,default=auth.user_id),
Field('title',requires=IS_LENGTH(100,1),label=“Board Title"),
Field(‘idea_a',requires=IS_LENGTH(75,1),label=“Idea A"),
Field(‘idea_b',requires=IS_LENGTH(75,1),label=“Idea B"),
Field('description','text',requires=IS_LENGTH(250,1),label=“Board Description"),
Field('contributors','integer',default='0'),
Field('status','integer',writable=False,readable=False,default='1'), #1=draft, 2=public
Field('created_on','datetime',writable=False,default=request.now))
#Web2py controllers
def CORS(f):
"""
Enables CORS for any action
"""
def wrapper(*args, **kwargs): #*args, **kwargs
if request.env.http_origin:
response.headers['Access-Control-Allow-Origin'] = request.env.http_origin
response.headers['Access-Control-Allow-Credentials'] = "true";
response.headers['Access-Control-Allow-Headers'] = "Authorization,Content-Type,data";
return dict()
return f(*args, **kwargs)
return wrapper
auth.settings.allow_basic_login = True
#CORS
#request.restful()
def api():
from gluon.serializers import json
response.view = 'generic.'+request.extension
def GET(*args,**vars):
patterns = 'auto'
parser = db.parse_as_rest(patterns,args,vars)
if parser.status == 200:
return dict(content=parser.response)
else:
raise HTTP(parser.status,parser.error)
def POST(table_name,**vars):
return json(db[table_name].validate_and_insert(**vars))
return dict()
def PUT(table_name,record_id,**vars):
return db(db[table_name]._id==record_id).update(**vars)
def DELETE(table_name,record_id):
return db(db[table_name]._id==record_id).delete()
return dict(GET=GET, PUT=PUT, POST=POST, DELETE=DELETE)
//CURL COMMAND - This Works!
curl -i --user somename#gmail.com:thepassword -H Accept:application/json -X POST http://127.0.0.1:8000/cc/default/api/myboard.json -H Content-Type: application/json -d 'userid=2&title=THE_TITLE&description=THE_DESCRIP&idea_a=THE 1st idea&idea_b=THE 2nd idea’
//AJAX CALL - Doesn't Work :(
var userid = 2;
var title = "THE_TITLE_HERE";
var description = "THE_DESCRIPTION_HERE"
var idea_a = "THE 1st idea";
var idea_b = "THE 2nd idea";
var userID = ’somename#gmail.com';
var password = ’thepassword';
var theData = "userid=2&title="+title+"&description="+description+”&idea_a=“+ideaA+”&idea_b=“+ideaB;
$.ajax({
type: 'POST',
headers: {"Authorization": "Basic " + btoa(userID + ":" + password)},
url: "http://127.0.0.1:8000/cc/default/api/myboard.json",
contentType: "application/json; charset=utf-8",
data: theData,
success: function (data,textStatus,jqXHR) {
alert(textStatus);
console.log(data);
},
error: function(){
alert("Cannot get data");
}
});
The database table does not update but the request runs successfully. It returns an empty object each time.. {}
Your wrapper function calls return dict(), which executes before the call to f(), so the decorated api function never gets called. Just remove that line.
Also, note that depending on the nature of the Ajax request, the browser might first make a preflight request. So, your decorator code will need to detect and respond to such requests with the appropriate headers.
Ok I solved this issue by doing three things.
1. I removed the CORS decorator code from the controller file - default.py
2. I inserted the below code at the top of the default.py controller file
if request.env.http_origin:
response.headers['Access-Control-Allow-Origin'] = request.env.http_origin;
response.headers['Access-Control-Allow-Methods'] = "POST,GET,OPTIONS";
response.headers['Access-Control-Allow-Credentials'] = "true";
response.headers['Access-Control-Allow-Headers'] = "Accept, Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Accept-Encoding";
I modified the api class to include the OPTIONS verb by adding the following at the bottom of the class, after the DELETE verb. The modified api class is as follows.. Note: The change is in the last 4 lines of code.
def api():
from gluon.serializers import json
response.view = 'generic.'+request.extension
def GET(*args,**vars):
patterns = 'auto'
parser = db.parse_as_rest(patterns,args,vars)
if parser.status == 200:
return dict(content=parser.response)
else:
raise HTTP(parser.status,parser.error)
def POST(table_name,**vars):
#return db[table_name].validate_and_insert(**vars)
#data = gluon.contrib.simplejson.loads(request.body.read())
return json(db[table_name].validate_and_insert(**vars))
return dict()
def PUT(table_name,record_id,**vars):
return db(db[table_name]._id==record_id).update(**vars)
def DELETE(table_name,record_id):
return db(db[table_name]._id==record_id).delete()
def OPTIONS(*args,**vars):
print "OPTION called"
return True
return dict(GET=GET,POST=POST,PUT=PUT,DELETE=DELETE,OPTIONS=OPTIONS)
And that's it. The issue was with web2py in that I needed to include the OPTIONS verb in the api call AND including the code for the headers directly at the top of the controller file instead of putting it in a wrapper seemed to work for me. Now everything connects and the database is updated.
reference url: https://github.com/OpenTreeOfLife/phylesystem-api/issues/26

Django REST API Logout request

For logging in, I'm doing something like:
function setHeader(xhr) {
// as per HTTP authentication spec [2], credentials must be
// encoded in base64. Lets use window.btoa [3]
xhr.setRequestHeader("Authorization", "Basic " + btoa(username + ':' + password));
}
$.ajax({type: "POST", url: AUTH_URL, beforeSend: setHeader}).
fail(function(resp){
console.log('bad credentials.')
}).
done(function(resp){
});
after which, I'm storing the Session in local storage.
However, for logging out, I'm unable to figure out how to use this session to send with the request header, so that django's : request.logout() logs out the user having that session id
For login you can add view similar to this one:
import json
import requests
from django.shortcuts import render_to_response
from django.http import HttpResponseRedirect
#csrf_protect
def login(request):
if request.method == "POST":
login = requests.post('http://your_url/api-token-auth/', data={'username': request.POST['username'], 'password': request.POST['password']})
response = json.loads(login.text)
if response.status_code == 200:
token = response['token']
request.session.flush()
request.session['user'] = request.POST['username']
if request.session.test_cookie_worked():
request.session.delete_test_cookie()
return HttpResponseRedirect("/")
else:
error = "Error"
request.session.set_test_cookie()
return render_to_response("login.html", {"error": error}, RequestContext(request))
For logout all you have to do in your view is:
def logout(request):
request.session.flush()
return HttpResponseRedirect('/')
On your API side, you have to define api-token-auth in urls: here is the tutorial for more informations
url(r'^api-token-auth/', 'rest_framework.authtoken.views.obtain_auth_token')
This way you will get your token for communication with the API. Beside TokenAuthentication you can define and SessionAuthentication. More about that you can find in the above tutorial
You are using HTTP Basic Authentication, which does not define a way to log users out. It is not tied to the Django session, so you can't clear that. You could potentially clear out the token from session storage, and send an invalid token, though the browser may opt to send the original credentials (untested).
There are quite a few questions about it on Stack Overflow. Your best bet looks like sending invalid credentials, hoping that the user's browser will invalidate any saved ones.
You may be able to use a form of token-based authentication, such as TokenAuthentication or OAuth, which will not be intercepted by the browser. This way you will not need to worry about logging users out, as the authentication is tied directly to requests made with the token.

Categories

Resources