Flask with Javascript - javascript

I have been developing a webserver application using Flask. So far, I created a small framework, where the users can draw some boxes in a image, using canvas and javascript. I keep the boxes information in a vector in javascript as well as the image information. However, all this data must be submitted and stored in a database the server side. Therefore, I have a button to submit all this content, but I have no idea how to retrieve the javascript data I have, i.e.: boxes and image information.
Is it possible to get the javascript information to submit like that? I have come up with some ideas such as printing the information in hidden HTML elements, or, maybe, using AJAX for sending the information to the server, but I don't think those are the "correct" methods for dealing with this problem in Flask. So, does anyone have a idea. Here follow part of my code that may seem relevant for understanding my problem:
Models.py: My classes here are a little different: Blindmap=Image, Label=boxes. My database is modelled using SQLAlchemy.
blindmap_label = db.Table('blindmap_label',
db.Column('blindmap_id', db.Integer, db.ForeignKey('blindmap.id', ondelete = 'cascade'), primary_key = True),
db.Column('label_id', db.Integer, db.ForeignKey('label.id', ondelete = 'cascade'), primary_key = True))
class Blindmap(db.Model):
__tablename__ = 'blindmap'
id = db.Column(db.Integer, primary_key = True)
description = db.Column(db.String(50))
image = db.Column(db.String)
labels = db.relationship('Label', secondary = blindmap_label, backref = 'blindmaps', lazy = 'dynamic')
def __init__(self, label = None, **kwargs):
if label is None:
label = []
super(Blindmap, self).__init__(**kwargs)
def add_label(self, label):
if label not in self.labels:
self.labels.append(label)
db.session.commit()
def __repr__(self):
return '<Blindmap %r:>' % (self.id)
class Label(db.Model):
__tablename__ = 'label'
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(50))
x = db.Column(db.Integer)
y = db.Column(db.Integer)
w = db.Column(db.Integer)
h = db.Column(db.Integer)
def __repr__(self):
return '<Pair %r:>' % (self.id)
My controllers information:
#app.route('/')
#app.route('/index')
def index():
blindmaps = db.session.query(Blindmap).all()
return render_template("index.html",
title = 'Home',
blindmaps = blindmaps)
#app.route('/new', methods = ['GET', 'POST'])
def new():
form = BlindmapForm()
if request.method=="POST":
if form.validate_on_submit():
blindmap = Blindmap(description=form.description.data)
redirect(url_for('index'))
return render_template("new.html",
title = 'New Blindmap',
form=form)

Try jQuery ajax:
function upload() {
// Generate the image data
var img= document.getElementById("myCanvas").toDataURL("image/png");
img = img.replace(/^data:image\/(png|jpg);base64,/, "")
// Sending the image data to Server
$.ajax({
type: 'POST',
url: '/upload', // /new
data: '{ "imageData" : "' + img + '" }',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function (msg) {
// On success code
}
});
}
Rest is get uploaded image data at server side using request.json['imageData'] and store it in database.
img = request.json['imageData'] # Store this in DB (use blob)

What you have to do is to use AJAX, the technique to "asynchronously" send requests and get responses from a server after the page is loaded on the client's browser, using JavaScript.
AJAX, in fact, stands for Asynchronous JavaScript And XML (even though it doesn't necessarily have to be neither completely asynchronous, nor you must resort to XML as a data exchange format). This is the standard way to access Web APIs, such as the one you crafted with Flask by exposing the ability to retrieve and persiste objects in your backend through URLs (the ones represented the the routes).
Modern browsers consistently expose the XMLHttpRequest constructor (MDN documentation) that can be used to create objects that allow the page to communicate with your Web server once it's loaded.
To improve cross-browser compatibility and code maintainability, you can resort to JavaScript frameworks that "wrap" the XMLHttpRequest with their own abstractions. I've been productively using jQuery for years for this purpose. In particular, in your case you would need the jQuery.ajax method (or, for POST operations, it's shorthand jQuery.post).
However I'll give you a small example of how to perform such request using vanilla JS so that you can understand what's going on in the browser even when using a framework:
// Create an instance of the XHR object
var postNewObject = new XMLHttpRequest();
// Define what the object is supposed to do once the server responds
postNewObject.onload = function () {
alert('Object saved!');
};
// Define the method (post), endpoint (/new) and whether the request
// should be async (i.e. the script continues after the send method
// and the onload method will be fired when the response arrives)
postNewObject.open("post", "/new", true);
// Send!
postNewObject.send(/* Here you should include the form data to post */);
// Since we set the request to be asynchronous, the script
// will continue to be executed and this alert will popup
// before the response arrives
alert('Saving...');
Refer to MDN for more details about using the XMLHttpRequest.

Related

Save Multiple Canvas Created Images to Server via AJAX with JavaScript

I am working on a form set for a client. In a nutshell:
The forms are filled out by my client’s customers by selecting different options on each form.
Each form can have multiple instances, depending on the customer.
At the end of the process, the customer can opt to either sign one or all the forms digitally or decline to sign them digitally and at the end of the process the forms are printed out and signed manually.
To accomplish this, I’ve created a signature plugin written in jQuery. Once the customer fills out the forms, they are presented each form separately. To sign the form digitally they simply tap (click) the signature block, a dialog with a canvas element appears, they sign the form and save it, the signature appears in the form, and they move on to the next form.
Here is the portion of the code that stores the completed signature and adds the image to the form:
$.sig = {
signatures: {},
}
function signatureSave() {
var canvas = document.getElementById("sigcanvas"),
dataURL = canvas.toDataURL("image/png");
document.getElementById($.sig.target).src = dataURL;
$.sig.signatures[$.sig.target].url = dataURL;
$.sig.signatures[$.sig.target].hasSignature = true;
};
The function is only called if the signature is saved, if there is no signature, the $.sig.signatures[$.sig.target].hasSignature remains false and the system skips the object.
This all works as intended, almost.
My problem lies in the process used to save the form information. If the customer does not sign any forms digitally the form information is simply saved and the forms are printed out, no need to save any signatures.
If the customer signs at least one form, though, the signatures must be sent to the server using the FormData() object.
I’ve used the FormData object in other projects for the client successfully, but only when the customer uploads one or more images to the browser using the input file element. It’s a pretty simple process because the resulting images have a img.file property that I send to the server.
Not so with a canvas object. All I get is the .src property, an any attempt to use anything from the resulting .png image that is created in the function above results in either a “cannot use a blob” or some other error.
Now I know if I have a single image, I can send it using AJAX with the following:
$.ajax({
type: "POST",
url: "script.php",
data: {
imgBase64: dataURL
}
})
Problem is that I am sending from one to x number of signatures.
Edit: I forgot to add this in. This is the function that is supposed to create the FormData object used to send the signatures to the server (and where my problem lies):
function getUploadData() {
var upl = new FormData();
$.each($.sig.signatures, function (e, u) {
if (u.hasSignature == true && u.url != null) {
var im = new Image();
im.src = u.url;
upl.append(u.target, im, u.target + '.png');
}
})
return upl;
}
I've tried all the tricks and nothing is working. The var im = new Image(); as well as the following line are just my latest ill fated attempt.
Picture perfect would be the ability to save the image information in the $.sig.signatures object so I can simply loop through any signatures that are signed, add them as elements of the FormData object, and then send the FormData object as the data for the AJAX call. As stated before, I use this method in other projects and works fine.
Does anyone know a way to do this?
Please note:
The server-side AJAX processor functions correctly.
The signature process works correctly (customer signs canvas, signature is displayed, signature information is stored).
All I need is how to send multiple images created using the canvas element in a FormData object to the server.
I know the answer is staring me right in the face, but I am just not getting it. Any hints or suggestions would be greatly appreciated!
Edit: Just a note. I've searched the entire afternoon for this and have found entries that either deal with sending multiple files using FormData and AJAX - but the files are uploaded to the browser (not created using Canvas), or single files sent to the server that are created using Canvas, but nothing about sending multiple files sent using FormData and AJAX that are created using Canvas. Oje!
As stated, the answer was staring me in the face, but I didn't see it because was looking behind the wrong door. FormData has nothing to do with it (Homer Dope Slap!).
Since I already have the data stored in $.sig.signature for each signature, I just need to send the information to the server as the data in the AJAX function. I updated my function above as shown:
function getUploadData() {
var upl = {};
$.each($.sig.signatures, function (e, u) {
if (u.hasSignature == true && u.url != null) {
upl[e] = u.url;
}
})
return upl;
}
Since the form information is sent as JSON I just add the signature info to the object that contains the form information, JSON.stringify it and send it on its way. This should work because the information retrieved above are strings.
Server side will look something like this:
$info = json_decode( $_POST['info'] );
// Various validation routines and checks
foreach( $info->signatures as $sig=>$data ):
$data = str_replace('data:image/png;base64,', '', $data);
$data = str_replace(' ', '+', $data);
$img = base64_decode($data);
// Do some processing, file naming, database saving and other general dodads
$success = file_put_contents( $file, $img );
endforeach;
The above function is still concept, I am reworking some of the code but this should work.
Credit is given to this post for opening my eyes:
post sending base64 image with ajaxpost sending base64 image with ajax
So question answered and yeah, I deserve a dope slap, but all comes out right in the end.
CAVEAT: Works like a charm.

Render template in Django view after AJAX post request

I have managed to send & receive my JSON object in my views.py with a POST request (AJAX), but am unable to return render(request, "pizza/confirmation.html"). I don't want to stay on the same page but rather have my server, do some backend logic on the database and then render a different template confirming that, but I don't see any other way other than AJAX to send across a (large) JSON object. Here is my view:
#login_required
def basket(request):
if request.method == "POST":
selection = json.dumps(request.POST)
print(f"Selection is", selection) # selection comes out OK
context = {"test": "TO DO"}
return render(request, "pizza/confirmation.html", context) # not working
I have tried checking for request.is_ajax() and also tried render_to_string of my html page, but it all looks like my mistake is elsewhere. Also I see in my terminal, that after my POST request, a GET request to my /basket url is called - don't understand why.
Here is my JavaScript snippet:
var jsonStr = JSON.stringify(obj); //obj contains my data
const r = new XMLHttpRequest();
r.open('POST', '/basket');
const data = new FormData();
data.append('selection', jsonStr);
r.onload = () => {
// don't really want any callback and it seems I can only use GET here anyway
};
r.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
r.send(data);
return false;
In your basket view function, you always render the template as response. You can pass the parameters to your js snippet via HttpResponse. After request completed and you have response in your js function, you can use window.location.href to redirect the page you want. You can also look this answer to get more information.

Passing variable between view and JS; Django

I am first passing a JS variable 'confirmed' to my Django view via POST request.
I then run a python script which takes this variable and does some processing.
Finally I want to pass it back to my html/JS so I can display the processed data.
I am currently trying to use Django sessions to achieve my goal but there is a '1 session delay', so the session variable which I update is returning as the value from the previous session.
Is there a better way I can pass the variable from my view to JS/a fix for my current solution?
VIEW:
def crossword(request):
if request.method == 'POST':
Session.objects.all().delete()
str_squares = (request.body).decode('utf-8')
squares = [int(i) for i in str_squares.split(',')]
letters = puzzle_solver.solve_puzzle(3, squares)
# print(letters)
for each_square in letters.keys():
request.session[each_square] = letters[each_square]
request.session.modified = True
return render(request, 'crossword.html')
JS:
// Output a list of active squares
var xhr = new XMLHttpRequest();
var generate = { Generate:function(){
var confirmed = [];
for (i=0; i<active_boxes.length; i++){
confirmed.push(active_boxes[ i ].id_num);
}
console.log(confirmed);
xhr.open("POST", "http://localhost:8000/", true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(c=confirmed);
console.log("{{ request.session.keys }}")
console.log("{{ request.session.values }}")
var tester = parse_session_keys("{{ request.session.keys }}");
console.log(tester);
solve_crossword(tester);
}};
Is there a reason why you are not sending the response directly back to JS? In your views.py, you can do
from django.http import JsonResponse
def crossword(request):
if request.method == 'POST':
str_squares = (request.body).decode('utf-8')
squares = [int(i) for i in str_squares.split(',')]
letters = puzzle_solver.solve_puzzle(3, squares)
return JsonResponse(letters)
return render(request, 'crossword.html')
After sending your request, wait and read the response directly as JSON then play with it in your JS code, as described in this post.
If there is any reason preventing you from doing this (for example there might be a form also makes POST requests to the same page thus you need to eventually land on rendering the crossword template), allocate another url for your api like /api/crossword
Your original idea of using session should not be used in this use case, especially with your Session.objects.all().delete() line (don't delete all session data without checking).

PythonAnywhere How to handle multiple "web workers" or processes

Summary of my website: A user fills in some information which after hitting "submit" the information is submitted to the backend via AJAX. Upon the back end receiving the information, it generates a DOCX using the information and serves that DOCX file back to the user.
Here is my AJAX Code in my HTML File
$.ajax({
type:'POST',
url:'/submit/',
data:{
data that I submit
},
dateType: 'json',
success:function() {
document.location = "/submit";
}
})
My Views Function for /submit/ that uses send_file to return file
def submit(request):
#Receive Data
#Create a File with the Data and save it to the server
return send_file(request)
def send_file(request):
lastName = get_last_name() +'.docx'
filename = get_full_path() # Select your file here.
wrapper = FileWrapper(open(filename , 'rb'))
response = HttpResponse(wrapper, content_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document')
response['Content-Disposition'] = 'attachment; filename=' + lastName
response['Content-Length'] = os.path.getsize(filename)
return response
This has worked flawlessly for sometime now. However I started having problems when I increased the amount of "web-workers"/processes from 1 to 4 in my hosting account. Whats happening is a different web-worker is being used to send the file, which is creating a new instance of the site to do that. The problem with that is that the new instance does not contain the file path that is created with the web worker that creates the file.
Like I said, this worked flawlessly when my webApp only had one "web worker" or one process. Now I only have roughly a 50% success rate.
Its almost like a process is trying to send the file before it has been created. Or the process does not have access to the file name that the process that created it does.
Any help would be much appreciated. Thanks!
Code Trying to send path_name through request and then back to the server.
Submit View returning file info back to ajax.
def submit(request):
# Receive DATA
# Generate file with data
lastName = get_last_name() +'.docx'
filename = get_full_path() # Select your file here.
return HttpResponse(json.dumps({'lastname': lastName,'filename':filename}), content_type="application/json")
Success Function of AJAX
success:function(fileInfo) {
name_last = fileInfo['lastname']
filepath= fileInfo['filepath']
document.location = "/send";
}
So can I get the fileINfo to send with the "/send" ?
Each web worker is a separate process. They do not have access to variables set in another worker. Each request could go to any worker so there is no guarantee that you'd be using the file name that was set for a particular user. If you need to transfer information between requests, you need to store it outside of the worker's memory - you could do that in a cookie, or in a database or a file.

On jQuery.post, I get the message: The method GET is not allowed for the requested URL

I have the following problem:
I work on a Flask application, and I want to pass some data to the server via AJAX. I am pretty new on this AJAX thing, so I can't get something right.
On my client side, when the user clicks on an icon, I want to pass some data via jQuery.post stored in the variable message:
jQuery("#icon_ID").click(function() {
var message = {
'GRAPH_TYPE': graphType
};
var _sendOnSuccess = function () {
}
var jqxhr = jQuery.post('/graph', message, _sendOnSuccess, 'json');
});
On my server side, I have the following code:
#app.route('/graph', methods = ['POST'])
#login_required
def physical_graph():
ret_data = request.form['GRAPH_TYPE']
return ""
All I want to do for now is to access the GRAPH_TYPE on the server side. However, when I click on the icon, I get the error message:
Method Not Allowed
The method GET is not allowed for the requested URL.
I really don't understand why Python tells me that I am using the GET method, when in fact I am using the POST method.
Can please someone help me with this? What should I do to solve this problem? If there's some other method I can use, feel free to give me advice of any kind. Just bear in mind that besides jQuery, I don't want to use other JavaScript library.
Thank you in advance!
It's because you are passing an object as data like
var message = {
'GRAPH_TYPE': graphType
};
In this case jQuery attempts to URL encode the object and by default sends with the data type application/x-www-form-urlencoded; charset=UTF-8 ans sends a GET request. To overcome this problem make sure that you’re passing jQuery a string for the data parameter and to do this you can use JSON.stringify like
var message = JSON.stringify({ "GRAPH_TYPE": graphType });

Categories

Resources