OK so I am still struggling on this one, here is my complete code after using #DrAgon suggestion. I have 3 files, the first one is my Python file that merely get the local time.
import datetime
import time
now = datetime.datetime.now()
print(now)
If I set this file up as:
import datetime
import time
while True:
now = datetime.datetime.now()
print(now)
time.sleep(2)
It refreshes and gets me the date every 2 seconds but when it's imported in to my flask project it takes over and just will not load the web page.
so I use the first version of the file that gets the local time. Next I have my flask Python page:
from flask import Flask, render_template, request, jsonify
from flask_bootstrap import Bootstrap
import Timereppper
import datetime
now = datetime.datetime.now()
app = Flask(__name__)
Bootstrap(app)
#app.route('/')
def hello():
return render_template('hello.html', myvar= now.second)
#app.route('/currentstatus')
def currentstatus():
return render_template('hello.html', myvar= now.second)
#app.route('/historic')
def historic():
return render_template('historic.html')
#app.route('/data')
def get_data():
mbdata = Timereppper.now() # Call your code that gets the data here
return jsonify(mbdata) # And return it as JSON
if __name__ == '__main__':
app.run(host= 'localhost', port=5000, debug=False, threaded=True)
and then i have my main hello.html file with the code suggested by #DrAgon at the bottom. This returns the text on the bottom of the main webpage "New Data Goes Here" and if I look at lcalhost:5000/data I get the date that was read at the when the flask server was first started. I want this date to update continuously or every few seconds and display on the main home page of the website. Can anyne show me where I am going wrong. I apologise I am new to flask.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title> Vibration Monitor</title>
<meta name="viewport" content="width=device-wi dth, initial-scale=1">
<link href="{{url_for('static', filename='css/bootstrap.min.css')}}"
rel="stylesheet">
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico')
}}">
<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>
</head>
<nav class="navbar navbar-expand-lg navbar-light bg-#808080">
<a class="navbar-brand" href= https://www.iotindustries.co.uk>
<img src="/static/img/IOT Industries Logo 1THIN.png" width="300" height="90"
class="d-inline-block" alt="">
</a>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link font-weight-bold" href="/currentstatus">Current
Status</a>
</li>
<li class="nav-item active">
<a class="nav-link font-weight-bold" href="/historic">Historic</a>
</li>
</ul>
</nav>
<div class="alert alert-secondary" role="alert"></div>
<div class="card-deck" style="width: 42rem;">
<div class="card text-white bg-dark mb-3" style="width: 16rem;">
<img class="card-img-top" src="/static/img/vibes1.png" alt="Vibration
Image">
<div class="card-body">
<h5 class="card-title">Current vibration level:</h5>
<h1 class="card-text font-weight-bold">15 mA</h1>
<a class="btn btn-success">Acknowledge</a>
</div>
</div>
<div class="card text-white bg-dark mb-3" style="width: 16rem;">
<img class="card-img-top" src="/static/img/timer.svg" alt="Timer Image">
<div class="card-body">
<h5 class="card-title">Estimated days until failure:</h5>
<h1 class="card-text font-weight-bold"> 3 Days {{myvar}} </h1>
<a class="btn btn-info" href="/historic">View Historic</a>
</div>
</div>
</div>
<body>
<div id="myDataDiv">New Data Goes Here</div>
</body>
<script>
var goGetNewData = function() {
$.ajax({
url: '/data',
dataType: 'json',
type: 'get',
success: function(e) {
$('#myDataDiv').html('New Data ' + e);
},
error: function(e) {
$('#myDataDiv').html('Error');
},
});
}
var waitTime = 10; // 10 seconds
var fetchInterval = window.setInterval(goGetNewData, waitTime*1000);
</script>
</html>
I have a Python file which is constantly reading a Modbus register every couple of seconds, using the Python sleep.() function. This value is getting logged to a google FireBase database.
What I would like to do is display this value read from the ModBus register on my Flask website. I just don't know how to do it. Am I able to either:
Get the updated ModBus value from the Python file, pass that in to "MyFlaskApp.py" continuously or at the click of a button in my HTML file.
Query the Firebase database and get this to display the latest written value either continuously or at the click of a button.
Which, if any of these, can be done and what would be the best method. Can anyone post an example of how I would be able to do this?
Thanks
Well If you want to do it on a click of a button, you can make the button part of an html form and have it post to your flask application:
#app.route('/update')
def val():
return render_template("index.html",data=data)
This would work, considering the value you want to pass to the html is called data.
To display the data passed through, your html should look like this:
<p>{{data}}</p>
Instead of updating the modbus value every two seconds, you could do it only when the button is clicked like so:
#app.route('/update')
def val():
#code to get the value
return render_template("index.html",data=data)
This way everytime you click the form button to get a new value, then the data is read from the database. Instead of using another file as an import and using datetime, this would make sure that your program not only saves memory but still returns the desired value.
In the now method, you should write to a txt file as such:
#get the data
f = open("mod.txt", w+)
f.write(data)
f.close
Then when you recive the button click in the flask app:
#app.route('/update')
def val():
f = open("mod.txt",r)
data = f.read()
f.close()
return render_template("index.html",data=data)
To make sure that the other program is running you can do this:
if __name__ == '__main__':
execfile('modbusreginfo.py')
app.run(host= 'localhost', port=5000, debug=False, threaded=True)
If you want to make the page reload every 2 seconds so there is no button click you can add this to your html:
<meta http-equiv="Refresh" content="2">
Here's one way.
You will need to create 2 routes in your MyFlaskApp.py
/home - This route will render an HTML template
/data - This route will return JSON (containing the data from your ModBus file OR from querying Firebase)
(you can name the routes whatever you want)
In the HTML template returned by the first route you will need a div (with an id) to render the data and some JavaScript to continuously fetch new data.
Then just point the Ajax GET at your /data endpoint and your on your way.
<body>
<div id="myDataDiv">New Data Goes Here</div>
</body>
<script>
var goGetNewData = function() {
$.ajax({
url: '/data',
dataType: 'json',
type: 'get',
success: function(e) {
$('#myDataDiv').html('New Data ' + e);
},
error: function(e) {
$('#myDataDiv').html('Error');
},
});
}
var waitTime = 10; // 10 seconds
var fetchInterval = window.setInterval(goGetNewData, waitTime*1000);
</script>
The /data route could look something like this.
from flask import jsonify
import Modbusreginfo # Import your other python code
#app.route('/data')
def get_data():
mbdata = Modbusreginfo.getModBusData() # Call your code that gets the data here
return jsonify(mbdata) # And return it as JSON
Related
I am trying to create youtube video downloader application using pytube and flask. All is done, except that a want to call pytube's stream download method from within the html script tag. How can i do it.
Here's my flask code
from flask import Flask, render_template, request
from pytube import YouTube
app = Flask(__name__)
#app.route("/")
def index():
return render_template("index.html", data=None)
#app.route("/download", methods=["POST", "GET"])
def downloadVideo():
if request.method == "POST":
url = request.form["videourl"]
if url:
yt = YouTube(url)
title = yt.title
thumbnail = yt.thumbnail_url
streams = yt.streams.filter(file_extension='mp4')
data = [title, thumbnail, streams, yt]
return render_template("index.html", data=data)
if __name__ == "__main__":
app.run(debug=True)
and here's my html code
<!DOCTYPE html>
<html>
<head>
<title> Youtube Downloader </title>
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="stylesheet" href="static/css/style.css">
</head>
<body>
<div class="main">
<div class="header">
<div>
<img src="static/img/icon.png" width="48" height="48">
<h2> Youtube Downloader </h2>
</div>
<div>
<p> Convert and download youtube videos </p>
<p> in MP4 for free </p>
</div>
</div>
{% if not data %}
<div class="dform">
<form action="http://127.0.0.1:5000/download", method="POST">
<div class="inputfield">
<input type="input" name="videourl" placeholder="Search or Paste youtube link here" autocomplete="off">
<button type="submit"> Download </button>
</div>
</form>
</div>
{% else %}
<div class="videoinfo">
<img src="" class="thumbnail">
<h2> {{data[0]}} </h2>
</div>
<div class="quality">
<select id="streams">
{% for stream in data[2][:3] %}
<option value="{{stream.itag}}"> {{stream.resolution}} </option>
{% endfor %}
</select>
</div>
{% endif %}
</div>
<script type="text/javascript">
const image = document.querySelector(".thumbnail");
const select = document.querySelector("select");
let url = `{{data[1]}}`;
if (image) {
image.src = `${url}`;
window.addEventListener('change', function() {
var option = select.options[select.selectedIndex].value;
console.log(option);
{% set stream = data[3].get_by_itag(option) %}
{% stream.download() %}
});
}
</script>
</body>
</html>
I am trying to download the video using itag when a user clicks an option in the select element by using pytube get_by_itag() method.
From what I understand you want to do two things. You want to create a route on your flask app that will let serve up the youtube video based on an itag, and you want to be able to call that route from javascript.
This answer shows how to create a route to download the video.
To call a url that starts a file download from javascript you'll need to use the fetch method and open that link into an iFrame. This answer covers it.
Let me know if that covers your question.
I have a Django webapp displaying a form. One of the fields is a FileField, defined via the Django model of the form:
From models.py:
class Document(models.Model):
...
description = models.CharField(max_length=100, default="")
document = models.FileField(upload_to="documents/", max_length=500)
The document file_field has an onchange ajax function attached that will parse the uploaded filename, check some database stuff depending on it, and populate other fields on the html-page with the results.
From forms.py:
class DocumentForm(forms.ModelForm):
class Meta:
model = Document
fields = ("document",)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["customer"] = forms.CharField(initial="", required=True)
self.fields["output_profile"] = forms.CharField(initial="", required=True)
self.fields["document"].widget.attrs[
"onchange"
] = "checkFileFunction(this.value, '/ajax/check_file/')"
From urls.py:
urlpatterns = [
#...
path("ajax/check_file/", views.check_file, name="ajax_check_file")
]
From views.py:
def check_file(request):
full_data = {"my_errors": []}
my_path = pathlib.Path(request.GET.get("file_path").replace("\\", os.sep))
# parse customer ID from file_path
# get data of customer from db
# assemble everything into full_data
return JsonResponse(full_data)
This is the full html page as displayed (copied from Chrome => show source and cleaned up the indentation & whitespaces some):
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<link href="/static/css/main.css" rel="stylesheet" type="text/css">
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/themes/base/jquery-ui.css"/>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.20/css/dataTables.jqueryui.css"/>
<script src="/static/js/jquery.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
<script type="text/javascript" src="https://cdn.datatables.net/1.10.20/js/jquery.dataTables.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/1.10.20/js/dataTables.jqueryui.js"></script>
<title>
Convert RES FILE
</title>
</head>
<body>
<header id="header">
<section class="top_menu_left">
Login
<a> | </a>
Logout
<a> | </a>
Edit User
<a> | </a>
Register
</section>
<section class="top_menu_right">
About Us
<a> | </a>
Contact Us
<a> | </a>
Submit an issue
<a> | </a>
Documentation
<a> | </a>
Home
</section>
<div id="topbanner" >
<img src="/static/banner_small.png" alt="" width="100%" height="150"/>
</div>
</header>
<aside id="leftsidebar">
<section class="nav_account">
<h4>Submit a New Request</h3>
<ul>
<li>Get Typing Results</li>
<li>Compare Typing Results</li>
<li>Convert Typing Format</li>
</ul>
</section>
<section class="nav_tools">
<h4>View Your Requests</h3>
<ul>
<li>View My Submissions</li>
</ul>
</section>
</aside>
<section id="main">
<p> </p>
<h2>Convert Typing Results to Format of Choice</h2>
<p> </p>
<h3>Upload a file to our database</h3>
<form method="post" enctype="multipart/form-data">
<input type="hidden" name="csrfmiddlewaretoken" value="qqIKcsAynuE35MQ37dvjF5XeIyfcEbHb3wjtgygGZaigQReNxHLQewoDKcEb8Roj">
<div id="div_id_document" class="form-group">
<label for="id_document" class=" requiredField">
Document<span class="asteriskField">*</span>
</label>
<div class="">
<input type="file" name="document" onchange="checkFileFunction(this.value, '/ajax/check_file/')" class="clearablefileinput form-control-file" required id="id_document">
</div>
</div>
<input type="hidden" id="id_description" name="description" value="">
<p> </p>
<div id="div_id_customer" class="form-group">
<label for="id_customer" class=" requiredField">
Customer<span class="asteriskField">*</span>
</label>
<div class="">
<input type="text" name="customer" readonly class="textinput textInput form-control" required id="id_customer">
</div>
</div>
<div id="div_id_output_profile" class="form-group">
<label for="id_output_profile" class=" requiredField">
Output profile<span class="asteriskField">*</span>
</label>
<div class="">
<input type="text" name="output_profile" readonly class="textinput textInput form-control" required id="id_output_profile">
</div>
</div>
<div class="form-group">
<div id="div_id_notify_me" class="form-check">
<input type="checkbox" name="notify_me" style="width:15px;height:15px;" class="checkboxinput form-check-input" id="id_notify_me">
<label for="id_notify_me" class="form-check-label">
Notify me
</label>
</div>
</div>
<p>
<button class="linkbutton" type="submit" id="submit_btn">Convert</button>
<button id="create-book" class="linkbutton" type="button" name="button" style="float: right;">Create an Output Profile</button>
</p>
</form>
<div class="modal fade" tabindex="-1" role="dialog" id="modal">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="form-modal-content">
</div>
</div>
</div>
<div class="modal fade" tabindex="-1" role="dialog" id="modal-check-res-file">
<div class="modal-dialog" role="document">
<div class="modal-content" id="form-modal-content-check-res-file">
</div>
</div>
</div>
<script>
var formAjaxSubmit = function(form, modal) {
$(form).on('submit', function (e) {
e.preventDefault();
console.log("submitting...");
var my_val = $("#id_profile_name").val();
var this_val = $("#confirm_save").val();
var res = this_val.split(",");
var this_val_contains_my_val = (res.indexOf(my_val) > -1);
if (this_val_contains_my_val === true) {
var conf = confirm("Are you sure want to overwrite an exsisting profile?");
}else {var conf = true;};
if (conf === true) {
$.ajax({
type: $(this).attr('method'),
url: "/new_customer_profile/",
data: $(this).serialize(),
success: function (xhr, ajaxOptions, thrownError) {
if ( $(xhr).find('.invalid-feedback').length > 0 ) {
$(modal).find('.modal-content').html(xhr);
formAjaxSubmit(form, modal);
} else {
$(modal).find('.modal-content').html(xhr);
}
},
error: function (xhr, ajaxOptions, thrownError) {
}
});
};
});
};
$('#create-book').click(function() {
console.log("hhallo");
$('#form-modal-content').load('/new_customer_profile/', function () {
var iam_alive = document.getElementById("modal");
// check if iam_alive is defined, this is required if a session expired -> in that case the modal is lost and it would redirect to an almost empty page.
if (iam_alive) {
$('#modal').modal('toggle');
formAjaxSubmit('#form-modal-body form', '#modal');
}
// if not iam_alive: redirect to login page
else {
window.location.replace('/accounts/login/');
}
});
});
$('#check-res-file').click(function() {
console.log("hhallo hier unten jetzt");
$('#form-modal-content-check-res-file').load('/check_res_file/', function () {
$('#modal-check-res-file').modal('toggle');
//formAjaxSubmit('#form-modal-body form', '#modal');
});
});
</script>
<script type="text/javascript">
$(document).ready(function() {
var mycell = document.getElementById("create-book");
mycell.style.display = "none";
});
</script>
<script>
function checkFileFunction(myfile, url) {
$.ajax({ // initialize an AJAX request
url: url, // set the url of the request (= localhost:8000/hr/ajax/load-cities/)
data: {"file_path": myfile},
dataType: 'json',
success: function (x) {
if (x.my_errors.length == 0) {
$('#id_customer').val(x.customer_name);
$('#id_output_profile').val(x.customer_profile);
$('#id_description').val(x.customer_file);
}else{
$('#id_customer').val("");
$('#id_customer').val("");
$('#id_output_profile').val("");
alert(x.my_errors);
var showme = function myFunction() {
var mycell = document.getElementById("create-book");
mycell.style.display = "block";
};
showme();
}
},
});
}
</script>
</section>
</body>
</html>
Now, I'm trying to test this with pytest via Selenium.
I can send the file path to the field via send_keys(). However, the onchange event seems not to be triggered. (It does work fine when I select the file manually.)
file_field = self.driver.find_element(By.NAME, "document")
file_field.clear()
file_field.send_keys(str(path/to/myfile))
This will register the file fine and it will be uploaded, but the onchange function never happens.
I have searched and it seems others also have encountered the problem of send_keys not triggering the onchange event. But I have not been able to implement any of the suggested solutions in my Python code. (I have not written the Django code for this app, I'm just the tester and my grasp on Django and javascript is not very good, yet. My native programming language is Python.)
The only solution I understood how to implement was sending a TAB or ENTER afterwards (file_field.send_keys(Keys.TAB)) to change the focus, but that triggers an
selenium.common.exceptions.InvalidArgumentException: Message: invalid argument: File not found
(The file I enterted does exist, the path is fine. I can successfully call .exists() on it.)
Simply selecting a different element after send_keys to shift the focus (i.e., customer_field.click()) does not trigger the onchange function of file_field, either.
How can I trigger an onchange event via Selenium from Python? Or otherwise make sure it is triggered?
First of all, your onchange specification is a bit kludgy and would be preferably specified as:
<input type="file" name="document" onchange="checkFileFunction(this.value, '/ajax/check_file/');">
I am using Selenium with the latest version of Chrome and its ChromeDriver under Windows 10 and have no problems with the onchange event being taken. This can be demonstrated with the following HTML document. If the onchange event is taken, then it should create a new div element with id 'result' that will contain the path of the filename selected:
File test.html
<!doctype html>
<html>
<head>
<title>Test</title>
<meta name=viewport content="width=device-width,initial-scale=1">
<meta charset="utf-8">
<script>
function checkFileFunction(value)
{
const div = document.createElement('div');
div.setAttribute('id', 'result');
const content = document.createTextNode(value);
div.appendChild(content);
document.body.appendChild(div);
}
</script>
</head>
<body>
<input type="file" name="document" onchange="checkFileFunction(this.value);">
</body>
</html>
Next we have this simple Selenium program that sends a file path to the file input element and then waits for up to 3 seconds (with the call to driver.implicitly_wait(3)) for an element with an id value of 'result' to be found on the current page and then prints out the text value. This element will only exist if the onchange event occurs:
from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument("headless")
options.add_experimental_option('excludeSwitches', ['enable-logging'])
driver = webdriver.Chrome(options=options)
try:
# Wait up to 3 seconds for an element to appear
driver.implicitly_wait(3)
driver.get('http://localhost/test.html')
file_field = driver.find_element_by_name("document")
file_field.clear()
file_field.send_keys(r'C:\Util\chromedriver_win32.zip')
result = driver.find_element_by_id('result')
print(result.text)
finally:
driver.quit()
Prints:
C:\Util\chromedriver_win32.zip
Now if your driver is different and that is the reason why the onchange event is not occurring and you do not wish to or cannot switch to the lastest ChromeDriver, then you can manually execute the function specified by the onchange argument. In this version of the HTML file, I have not specified the onchange argument to simulate the situation where specifying it has no effect:
File test.html Version 2
<!doctype html>
<html>
<head>
<title>Test</title>
<meta name=viewport content="width=device-width,initial-scale=1">
<meta charset="utf-8">
<script>
function checkFileFunction(value)
{
const div = document.createElement('div');
div.setAttribute('id', 'result');
const content = document.createTextNode(value);
div.appendChild(content);
document.body.appendChild(div);
}
</script>
</head>
<body>
<input type="file" name="document">
</body>
</html>
And the new Selenium code:
from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument("headless")
options.add_experimental_option('excludeSwitches', ['enable-logging'])
driver = webdriver.Chrome(options=options)
try:
# Wait up to 3 seconds for an element to appear
driver.implicitly_wait(3)
driver.get('http://localhost/test.html')
file_field = driver.find_element_by_name("document")
file_field.clear()
file_path = r'C:\Util\chromedriver_win32.zip'
file_field.send_keys(file_path)
# Force execution of the onchange event function:
driver.execute_script(f"checkFileFunction('{file_path}');")
result = driver.find_element_by_id('result')
print(result.text)
finally:
driver.quit()
Update
I guess you missed <script src="/static/js/jquery.js"> in the <head> section, which appears to be jQuery. But I would have thought that with this script tag being in the <head> section that jQuery would have to be loaded by time Selenium found the file element. So I confess that your getting a javascript error: $ is not defined is somewhat surprising. I can only suggest now that you try loading it manually as follows as detailed in the code below.
I have re-iterated the 3 things you should try in order in the code below moving on to the next approach if the previous one does not work:
Give jQuery time to load before sending keystrokes to eliminate the $ not defined error.
Manually loading jQuery before sending keystrokes.
Manually executing the checkFileFunction.
# 1. Give jQuery time to load before sending keystrokes:
import time
time.sleep(3)
# 2. if the above sleeping does not work,
# remove the above call to sleep and manually load jQuery.
# Specify where the home page would be loaded from:
document_path = '/home/usr/account/public_html' # for example
jQuery_path = document_path = '/static/js/jQuery.js'
with open(jQuery_path, 'r') as f:
jQuery_js = f.read()
self.driver.execute_script(jQuery_js)
# Send keystrokes:
file_path = str(path/to/myfile)
file_field.send_keys(file_path)
# 3. Execute the following if the onchange doesn't fire by itself:
self.driver.execute_script(f"checkFileFunction('{file_path}', '/ajax/check_file/');")
As it turns out, the actual problem was that my manual test were done on the Django app served via python manage.py runserver. This calls some hidden Django magic, including collecting the statics files (css, jQuery.js etc.) under the hood.
I now learned that, in order to serve a Django app on a proper server, one needs to first call python manage.py collectstatic. This will generate a static folder in the parent directory, which contains all the static files and also an explicit jQuery.js.
Then, when Selenium is run, it will find that static folder and the jQuery.js file therein. And then, everything works as expected, including onchange.
So the problem was, that this parent static folder was missing, which I never saw because serving the website via python manage.py runserver doesn't need it.
<html>
<head>
<title>Addition</title>
<script>
function display(id_name,result_name){
document.getElementById(result_name).innerHTML = document.getElementById(id_name).value;
}
function calculate(id1,id2,result_id) {
document.getElementById(result_id).innerHTML = parseInt(document.getElementById(id1).value)+parseInt(document.getElementById(id2).value)
}
</script>
</head>
<body>
<div class="input">
Enter 1st Number:
<input type="text" id="input1_text">
<button type="button" onclick="display('input1_text','input1')">Enter 1st Number</button>
<span id="input1"></span>
</div>
<div class="input">
Enter 2nd Number:
<input type="text" id="input2_text">
<button type="button" onclick="display('input2_text','input2')">Enter 2nd Number</button>
<span id="input2"></span>
</div>
<div class="result">
<button type="button" onclick="calculate('input1_text','input2_text','result_value')">Calculate</button>
<span id="result_value"></span>
</div>
</body>
</html>
so in the above code i am not only adding 2 nubmers but also displaying the numbers after pressing the button.so now what i am looking for is to make this using flask framework on pressing buttons their respective functions should be fired up and data should be updated.I have tried using forms so the problem is on clicking button of 1st number to display is whole page is refreshing and i am losing whole data.so now how do i write those functions in python and also making sure that the page should not reload.Also is it possible to reuse the display function based on the paramters rather than hard coding and writing 2 display functions for 2 numbers seperately
Unless you need to use the user inputted values in the backend of your application, you can simply perform the calculations in the front-end:
<html>
<body>
<p>Input first number:</p>
<input type='text' class='first_val'>
<p>Input second number:</p>
<input type='text' class='second_val'>
<div class='results'></div>
<button type='button' class='calculate'>Calculate</button>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script>
$(document).ready(function() {
$('.calculate').click(function() {
var val1 = parseInt($('.first_val').val());
var val2 = parseInt($('.second_val').val());
var val3 = val1+val2;
$('.results').html('The result is '+val3);
});
});
</script>
</html>
Edit: using Python in the backend, ajax can be utilized:
index.html:
<html>
<body>
<p>Input first number:</p>
<input type='text' class='first_val'>
<p>Input second number:</p>
<input type='text' class='second_val'>
<div class='results'></div>
<button type='button' class='calculate'>Calculate</button>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js">
</script>
<script>
$(document).ready(function() {
$('.calculate').click(function() {
var val1 = $('.first_val').val();
var val2 = $('.second_val').val();
$.ajax({
url: "/get_numbers",
type: "get",
data: {val1: val1, val2:val2},
success: function(response) {
$(".results").html(response.packet);
},
error: function(xhr) {
//Do Something to handle error
}
});
});
});
</script>
</html>
Then, in the Python app:
#app.route('/', methods=['GET', 'POST'])
def home():
return flask.render_template('index.html')
#app.route('/get_numbers')
def get_values():
value1 = flask.request.args.get('val1')
value2 = flask.request.args.get('val2')
return flask.jsonify({'data':f'<p>The result is: {value1+value2}</p>'})
You should build an flask API function: a GET route which accepts the argument from your form and returns a JSON response object.
Then in your HTML when you click the button that should perform an AJAX GET quesry accessing your API route and return the values to that page. This avoids having Flask re-render your page and returns the data directly to your existing client web browser.
To make it easy to manipulate data dynamically in the webpage I suggest a JS framework like angular, react or vue. Personally I prefer vue since it can be self contained, loaded as script into the page and the setup is often minimal. The docs are also very easy and you can easily see how to link form input controls.
I want to display a table - which is a pandas dataframe - as a DataTable. In the simplified example below, I read two numbers provided by a user, that determine the row and column number of the table. The number of elements of this table is then displayed correctly, however, the table does not appear.
The problem is, I think, that I pass the table in the wrong way. When I try
return jsonify(number_elements=a * b,
my_table=df)
I get the error
anaconda2/lib/python2.7/json/encoder.py", line 184, in default
raise TypeError(repr(o) + " is not JSON serializable")
TypeError: 0 1 2 3 0 51 35 10 84 1 30 60 79 24 is not JSON
serializable
if I use
return jsonify(number_elements=a * b,
my_table=df.to_json())
then there is no error but the table is still not displayed.
How would I do this correctly?
My index.html file looks like this:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<link href="https://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css"
rel="stylesheet">
<link href="https://cdn.datatables.net/1.10.16/css/jquery.dataTables.min.css"
rel="stylesheet">
<script type=text/javascript>
$(function() {
$('a#calculate').bind('click', function() {
$.getJSON('/_get_table', {
a: $('input[name="a"]').val(),
b: $('input[name="b"]').val()
}, function(data) {
$("#elements").text(data.number_elements);
$("#a_nice_table").DataTable(data.my_table);
});
return false;
});
});
</script>
</head>
<body>
<div class="container">
<div class="header">
<h3 class="text-muted">Create a pretty table</h3>
</div>
<div>
<p>Number of rows</p>
<input type="text" size="5" name="a" value="2">
<p>Number of columns</p>
<input type="text" size="5" name="b" value="4">
<p>get a pretty table</p>
<p>Result</p>
<p>Number of elements:</p>
<span id="elements">Hallo</span><br>
<span id="a_nice_table">Here should be a table</span>
</div>
</div>
</body>
</html>
And my file app.py looks like this:
from flask import Flask, render_template, request, jsonify
import pandas as pd
import numpy as np
# Initialize the Flask application
app = Flask(__name__)
#app.route('/')
def index():
return render_template('index.html')
#app.route('/_get_table')
def get_table():
a = request.args.get('a', type=int)
b = request.args.get('b', type=int)
df = pd.DataFrame(np.random.randint(0, 100, size=(a, b)))
return jsonify(number_elements=a * b,
my_table=df)
if __name__ == '__main__':
app.run(debug=True)
Here's my implementation. I did some optimizations such as moving your js files to the end of the HTML:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<link href="https://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.datatables.net/1.10.16/css/jquery.dataTables.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="header">
<h3 class="text-muted">Create a pretty table</h3>
</div>
<div>
<p>Number of rows</p>
<input type="text" size="5" name="a" value="2">
<p>Number of columns</p>
<input type="text" size="5" name="b" value="4">
<p>get a pretty table</p>
<p>Result</p>
<p>Number of elements:</p>
<span id="elements">Hallo</span><br>
<table id="a_nice_table">Here should be a table</table>
</div>
</div>
<script src="https://code.jquery.com/jquery-1.12.4.js" type="text/javascript"></script>
<script src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
var table = null;
$('a#calculate').bind('click', function() {
$.getJSON('/_get_table', {
a: $('input[name="a"]').val(),
b: $('input[name="b"]').val()
}, function(data) {
$("#elements").text(data.number_elements);
if (table !== null) {
table.destroy();
table = null;
$("#a_nice_table").empty();
}
table = $("#a_nice_table").DataTable({
data: data.my_table,
columns: data.columns
});
});
return false;
});
});
</script>
</body>
</html>
app.py
from flask import Flask, render_template, request, jsonify
import pandas as pd
import numpy as np
import json
# Initialize the Flask application
app = Flask(__name__)
#app.route('/')
def index():
return render_template('index.html')
#app.route('/_get_table')
def get_table():
a = request.args.get('a', type=int)
b = request.args.get('b', type=int)
df = pd.DataFrame(np.random.randint(0, 100, size=(a, b)))
return jsonify(number_elements=a * b,
my_table=json.loads(df.to_json(orient="split"))["data"],
columns=[{"title": str(col)} for col in json.loads(df.to_json(orient="split"))["columns"]])
if __name__ == '__main__':
app.run(debug=True)
What I modified:
Added the js file to render the DataTable.
Moved the js files down to the bottom of the HTML.
Added a check in the js to destroy and clear columns when refreshing the data with new data.
Used the to_json method with orient of split to generate the json data for DataTables.
Also had to add a columns json string for DataTables to consume, which is dynamically set after using to_json
Here's how to use panda's to_html for generating the table:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<link href="https://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.datatables.net/1.10.16/css/jquery.dataTables.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="header">
<h3 class="text-muted">Create a pretty table</h3>
</div>
<div>
<p>Number of rows</p>
<input type="text" size="5" name="a" value="2">
<p>Number of columns</p>
<input type="text" size="5" name="b" value="4">
<p>get a pretty table</p>
<p>Result</p>
<p>Number of elements:</p>
<span id="elements">Hallo</span><br>
<div id="mytablediv">Here should be a table</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-1.12.4.js" type="text/javascript"></script>
<script src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
var table = null;
$('a#calculate').bind('click', function() {
$.getJSON('/_get_table', {
a: $('input[name="a"]').val(),
b: $('input[name="b"]').val()
}, function(data) {
$("#elements").text(data.number_elements);
if (table !== null) {
table.destroy();
table = null;
$("#a_nice_table").empty();
}
$("#mytablediv").html(data.my_table);
table = $("#a_nice_table").DataTable();
});
return false;
});
});
</script>
</body>
</html>
app.py
from flask import Flask, render_template, request, jsonify
import pandas as pd
import numpy as np
# Initialize the Flask application
app = Flask(__name__)
#app.route('/')
def index():
return render_template('index2.html')
#app.route('/_get_table')
def get_table():
a = request.args.get('a', type=int)
b = request.args.get('b', type=int)
df = pd.DataFrame(np.random.randint(0, 100, size=(a, b)))
return jsonify(number_elements=a * b,
my_table=df.to_html(classes='table table-striped" id = "a_nice_table',
index=False, border=0))
if __name__ == '__main__':
app.run(debug=True)
Differences from former implementation:
In the HTML, I had to add a parent div in order to hold the generated HTML table. In this case, I called it mytablediv.
In the HTML on the JS side, I have to basically modify the HTML content of the mytablediv after I generate my data. This HTML content comes from the to_html output.
In the HTML on the JS side, I didn't have to pass anymore data into the DataTable function because that would be handled with HTML code.
In app.py, I had to use a hackey method for pandas to generate an HTML ID tag. The ID tag lets JS know what element to modify. I used the solution from here.
In app.py, because I'm now generating HTML, I have to also explicitly specify other table style options like border=0 and index=False to mimic the former implementation.
Shouldn't you generate an html table first ? Taking advantage of the pandas.DataFrame.to_html() function ? Indeed, the documentation of DataTables show an example using an html table.
This question already has answers here:
How to get POSTed JSON in Flask?
(13 answers)
jQuery posting JSON
(3 answers)
Closed 5 years ago.
I have seen similar posts (which is how i have arrived at this current implementation) however i cannot get the below code to work. I want to:
Receive input from the user (badge id)
POST to the flask webserver via ajax
process the badge id and send back the result
receive the reply and process via javascript to be outputted via HTML onto the screen.
What is currently happening:
500 error TypeError: 'NoneType' object is not subscriptable.
When i use postman to submit a JSON to flask the reply works perfectly.
index.html
<!DOCTYPE html>
<html>
<head>
<title>App</title>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.2.4.min.js"></script>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
<script src="{{ url_for('static', filename='form.js') }}"></script>
</head>
<body>
<div class="container">
<br><br><br><br>
<form class="form-inline">
<div class="form-group">
<input type="text" class="form-control" id="badge" name ="badgeinput" placeholder="Badge Scan..">
<button type="submit" class="btn btn-default">Submit</button>
<br>
<br>
<div class="container">
<span name="result" id="result">... </span>
</div>
</div>
</form>
<br>
</body>
</html>
form.js:
$(document).ready(function() {
$('form').on('submit', function(event) {
$.ajax({
data : {
badge_ID: $('input[name="badgeinput"]').val(),
},
type : 'POST',
url : '/process'
})
.done(function(data) {
document.getElementById('#result').innerHTML = data.result;
});
event.preventDefault();
});
});
Flask:
from flask import Flask, render_template, request, jsonify
app = Flask(__name__)
#app.route('/')
def index():
return render_template('index.html')
#app.route('/process', methods=['POST'])
def process():
badge = request.json['badge_ID']
one = "Test"
new_badge_id = badge + one
return jsonify({'badge_ID' : new_badge_id})
if __name__ == '__main__':
app.run(host="localhost", port=4000, debug=True)
I'd say it's your input that is the problem.
You should have a check if request.json['badge_ID'] has any value.
You could also use developer tools (F12) in your browser to verify that the data you're sending is correct, checking post header in the network tab.
You haven't specified your post data-type, so it's better to consider it as being form not JSON.
you could set the type to post and be a bit safe about not getting data using :
request.form.get('badge_ID', None)
then, it seems that you have a typo in your jquery.done part.
return jsonify({'badge_ID' : new_badge_id})
since your result is
{"badge_ID": new_badge_id}
you don't have any data.result and you should try data.badge_ID
since in your comment you mentioned that you don't have None badge_ID in your view, so there is a good chance that changing this part in your code would fix it.
PS:
i would change your JS code to Jquery like this:
$("#result").html(data.badge_ID);
but your own JS should work just fine.