Reputation: 2679
We're creating a web application using Flask. On one html page, users answer some questions (using <form>
). During this process, they are asked to click a button on the page for a few times when they receive random popup notifications. In the .html
file, we write javascript functions to save the timestamps of users clicking on the button into an array.
We hope to store both the user's answers and the timestamps of clicking the button. Now from .html
to .py
, we pass answers using request.form
and pass the array of timestamps using ajax json (request.json
). The request.form
works very well but the request.json
always gives a None
. We have tried request.get_json()
, request.get_json(force=True)
, request.get_json(silent=True)
, but none of them works.
Our codes are like the following:
in trial.py
:
from flask import (Blueprint, flash, g, redirect, render_template, request, url_for, jsonify, json)
from werkzeug.exceptions import abort
import csv
from flaskr.db import get_db
bp = Blueprint('trial', __name__, url_prefix='/trial')
@bp.route('', methods=('GET', 'POST'))
def trial():
if request.method == 'POST':
if "Q1" in request.form:
## this part retrieves data from request.form
## skip the details in the following
db = get_db()
db.execute(
'INSERT INTO trial (...)', (...))
db.commit()
timestamps = request.json
print(timestamps) ### This is always a None
return render_template('study/trial.html')
In trial.html
:
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Trial{% endblock %}</h1>
{% endblock %}
{% block content %}
<!-- Skip non-related details in the following -->
<button class="NotificationButton" onclick="popup()">I Got A Notification</button>
<div>
<form name="TrialQuestions" id="TrialQuestions" method="post">
<!-- skip the details of the questions and input for answers -->
</form>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="{{url_for('static', filename='jquery.js') }}">\x3C/script>')</script>
<script>
var timestamps = {"trial":[]};
function playAudio() {
<!-- skip non-related details -->
timestamps["trial"].push(remaining);
}
function popup(){
<!-- skip non-related details -->
timestamps["trial"].push(remaining);
}
function timeUp(){
document.TrialQuestions.submit();
$.ajax({
type: "POST",
url: "{{ url_for("trial.trial") }}",
contentType: "application/json",
data: JSON.stringify(timestamps),
dataType: "json",
success: function(response) {
console.log(response);
},
error: function(err) {
console.log(err);
}
});
alert("Time Is Up! Your Answers Have Been Submitted!");
}
</script>
{% endblock %}
The console.log(response);
in trial.html
works well, but the timestamps = request.json
in trial.py
always gives a None
. We have searched and tried the suggestions from the following posts but none of them solves the problem:
Can anyone give some hints? Thanks a lot!
Upvotes: 1
Views: 1203
Reputation: 8572
Your form submit results in the page being reloaded and the template being transmitted again as html from the server.
An asynchronous javascript request is only ended when the callback for success or error is called. The transmission of the data is not waited for.
function timeUp() {
console.log("sending data to " + "{{ url_for('trial.trial') }}")
$.ajax({
type: "POST",
url: "{{ url_for('trial.trial') }}",
contentType: "application/json",
data: JSON.stringify(timestamps),
dataType: "json",
success: function(response) {
console.log(response);
// The data has now been successfully transferred.
// document.TrialQuestions.submit();
},
error: function(err) {
console.log(err);
}
});
}
When specifying the data type, the query expects exactly this as an answer. See also here.
For this reason, I am returning a response in json format.
from flask import render_template, request, jsonify
@bp.route('/', methods=('GET', 'POST'))
def trial():
if request.method == 'POST':
print('got a post request')
if request.is_json: # application/json
# handle your ajax request here!
print('got json:', request.json)
return jsonify(success=True) # answer as json
else:
# handle your form submit here!
print(request.form)
return render_template('trial.html')
AJAX has the advantage that you can send and load data sets without reloading and rendering the whole page.
Within a transmission, the data is formatted in a predefined format. When you submit a form you use the type "application/x-www-form-urlencoded" or "multipart/form-data". For JSON it is "application/json".
Depending on the selected format, either the query "request.json" returns a "None" or "request.form" is an empty dictionary.
The structure of your data set depends on your requirements.
The following is a suggestion that is similar to the original approach of a form.
function timeUp() {
const data = {
"data": $("form[name='TrialQuestions']").serializeArray().map(elem => {
return [elem["name"], elem["value"]];
}),
"ts": timestamps,
};
$.ajax({
type: "POST",
url: "{{ url_for('trial.trial') }}",
contentType: "application/json",
data: JSON.stringify(data),
dataType: "json",
success: function(response) {
console.log(response);
},
error: function(err) {
console.log(err);
}
});
}
Should you just want to send a form, for example because you want to do a redirect. Add one or more hidden fields to the form and fill in their value before submitting.
<form name="TrialQuestions" method="post">
<label for="q1">Question 1</label>
<input name="q1" id="q1" value="a1" />
<input name="ts" type="hidden" />
</form>
<script>
function timeUp() {
document.forms["TrialQuestions"]["ts"].value = timestamps.join();
document.forms["TrialQuestions"].submit();
}
</script>
Upvotes: 1
Reputation: 2679
... update to the question from myself.
As posted in the question, request.form
can work but request.json
will give a "None".
Following Detlef's approach, according to what I tried on my side, request.json
can work but request.form
will give a "None". Please correct me if I mistaken anything.
So, now I propose a trade-off approach: package the form and the timestamps into one ajax JSON object. The code is now like this:
<div>
<form name="TrialQuestions" id="TrialQuestions">
<label for="Q1">Question 1</label>
<input name="Q1" id="Q1">
<!-- skip the questions -->
</form>
</div>
<script>
var TrialQuestionAnswers = [];
var timestamps = [];
var formAndTimestamps = [];
function timeUp(){
TrialQuestionAnswers = [document.getElementById("Q1").value, ..., document.getElementById("Q13").value];
formAndTimestamps.push(TrialQuestionAnswers);
formAndTimestamps.push(timestamps);
$.ajax({
type: "POST",
url: "{{ url_for("trial.trial") }}",
contentType: "application/json",
data: JSON.stringify(formAndTimestamps),
dataType: "json",
success: function(response) {
console.log(response);
},
error: function(err) {
console.log(err);
}
});
alert("Time Is Up! Your Answers Have Been Submitted!");
}
</script>
It can work, but maybe this is not the most efficient solution. Better idea is much appreciated!
Upvotes: 0