Sending Data from Python (Flask) to Javascript? - javascript

I am trying to implement a drag and drop file uploader on my website. Files are uploaded immediately after they are dropped, and I would like to generate a URL with flask that will pop up under the previews. I am using dropzone.js. In the documentation for dropzone a sample is provided as a guide for sending data back from the server to be displayed after a file uploads. https://github.com/enyo/dropzone/wiki/FAQ#i-want-to-display-additional-information-after-a-file-uploaded
However, when I try to use url_for in the inline Javascript in my Jinja template that creates the dropzone, I am getting back a link that looks like /%7Bfilename%7D
Just to be sure I popped a quick print statement in there for the URL, and it comes out fine in the console.
My uploader in python:
def upload_file():
if request.method == 'POST':
file = request.files['file']
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
if is_image(file.filename): # generates a shortened UUID name for the image
filename = shortuuid.uuid()[:7] + "." + file.filename.rsplit(".", 1)[1]
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
#app.route ('/<filename>')
def uploaded_image(filename):
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
and the inline JS in my index.html template:
<script>
var mydropzone = new Dropzone(document.body, {
url: "{{url_for('upload_file')}}",
previewsContainer: "#previews",
clickable: "#clickable",
init: function() {
this.on("success", function(file, responseText) {
var responseText = " {{ url_for('uploaded_image', filename='{filename}')}} ";
var span = document.createElement('span');
span.setAttribute("style", "position: absolute; bottom: -50px; left: 3px; height: 28px; line-height: 28px; ")
span.innerHTML = responseText;
file.previewTemplate.appendChild(span);
});
}
});
Am I missing something fundamental here? Do I need to use something like JSON/Ajax (never worked with these but Googling always brought them up), because the URL is data send back from the server?

Simply:
Return the file path to the client:
def upload_file():
if request.method == 'POST':
# ... snip ...
return url_for('uploaded_image', filename=filename)
Remove the var responseText line in your on('success') function, since you will be getting the URL back in the response.

You are very close to getting it, but you will have to send the URL over JSON.
The issue is that you call url_for('uploaded_image') when the page first loads (in the Jinja template), before the URL is actually available. It is thinking you are asking for the url for a file called {filename}.
Try returning a JSON response from your POST request which has the new URL:
return jsonify({'fileURL':url_for('uploaded_image', filename=filename)})
From there, you can do whatever you would like with JS. What you have should work, just get the URL from responseText.
EDIT: Fixed return.

Related

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.

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.

Saving an audio blob as a file in a rails app

I am using recorder.js and have successfully gotten to the point where I can record an audio snippet and play it back over and over again to my heart's content. But now I am trying to make a post of this "blob" (which I know contains proper data since it's playing back correctly) as an audio wav file in my /public folder. My issue is that I am not sure how to deal with the blob and actually post its content. This is my code:
function sendWaveToPost1() {
console.log(savedWAVBlob);
$.ajax({ url: '/worm/save',
type: 'POST',
beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))},
data: 'someData=' + savedWAVBlob,
success: function(response) {
console.log("success");
}
});
}
Controller:
class WormController < ApplicationController
def create
end
def
save
audio = params[:someData]
p audio
save_path = Rails.root.join("public/audioFiles")
# Open and write the file to file system.
File.open(save_path, 'wb') do |f|
f.write audio.read
end
render :text=> 'hi'
end
end
That code was based on this post: Save audio file in rails.
But although I am posting something, it seems to be a string instead of the actual bytes of the audio file. This is the rails console error I receive:
NoMethodError (undefined method `read' for "[object Blob]":String):
Any ideas? I feel like I must be missing some encoding step, or maybe I'm just completely misunderstanding what a blob is. Why can't I just post it directly like this?
You need to convert blob to base64url and send it in data by ajax to upload in rails it is working for me
var reader = new window.FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function() {
var base64data = reader.result;
var savedWAVBlob=base64data
console.log(savedWAVBlob );
}
also in controller
require 'base64'
save_path = Rails.root.join("public/audio")
unless File.exists?(save_path)
Dir::mkdir(Rails.root.join("public/audio"))
end
data=params[:url]
audio_data=Base64.decode64(data['data:audio/ogg;base64,'.length .. -1])
File.open(save_path+"_audio", 'wb') do |f| f.write audio_data end
current_user.user_detail.audio=File.open(save_path+"_audio")
current_user.user_detail.audio_content_type="application/octet-stream"

Flask with 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.

Opening a JSON file from javascript

I have a C# functoin in my MVC application that returns a JSON representation of a csv file. I am trying to catch that in javascript and open the file. Basically I want the browser do its thing and let the user decide if he want to open it, save it or cancel. I am unable to get that popup to ask me to open the file. Below are the functions
C# Function
[HttpPost]
public ActionResult ExportToCsv(string fileContents, string fileName)
{
fileContents = fileContents.Replace("-CARRIAGE-", "\r\n");
return Json(new { url = File(Encoding.UTF8.GetBytes(fileContents), "text/csv", fileName) }); ;
}
This is the javascript where I am making the ajax call to the function
$("#btnExport").click(function (event) {
event.preventDefault();
var csv = table2csv(noteTypeTable, "full", "Table.dataTable", "noteTypes");
$.ajax({
url: "/Admin/Admin/ExportToCsv/?fileContents=" + csv + "&fileName=NoteTypes.csv",
type: 'Post',
success: function (result) {
window.open(result.url);
}
});
});
I know I am missing something. Can someone please help.
EDIT
After reading through all the potential answers and comments, this is what I am trying to achieve. So if my code is all horribly wrong please let me know.
I have a grid and I have an export to excel button. I have a method that converts the data i want into comma delimited text in javascript itself. I need to present this to the user as a downloadable csv file. For this I was creating the File object using the controller method. The previous incarnation was a Get method and I faced limitations due to querystring length restrictions. So I tried converting it to a POST method and it is not working.
This is the previous version of the code that works for smaller amounts of data
Javascript
$("#btnExport").click(function (event) {
event.preventDefault();
var csv = table2csv(noteTypeTable, "full", "Table.dataTable", "noteTypes");
window.location.href = "/Admin/Admin/ExportToCsv/?fileContents=" + csv + "&fileName=NoteTypes.csv";
});
C# Function
[HttpGet]
public ActionResult ExportToCsv(string fileContents, string fileName)
{
fileContents = fileContents.Replace("-CARRIAGE-", "\r\n");
return File(Encoding.UTF8.GetBytes(fileContents), "text/csv", fileName);
}
Hope this now gives you all more context. Basically I needed to convert my GET method to a POST method and use it from Javascript.
If you use ajax, you're expected to handle the result in code. But you can't trigger a file download (directly) that way.
Instead, create a (hidden) form and post it to a (hidden) iframe (by giving the iframe a name and specifying that as the target of the form), making sure that the response specifies the header Content-Disposition: attachment. That will trigger the browser to offer to save the file. Optionally in the header you can suggest a filename for the file by adding ; filename="fname.ext" to the header value. E.g. Content-Disposition: attachment; filename="fname.ext".
The client-side looks something like this:
$("#btnExport").click(function (event) {
event.preventDefault();
var csv = table2csv(noteTypeTable, "full", "Table.dataTable", "noteTypes");
var frame = $('iframe[name="formreceiver"]');
if (!frame.length) {
frame = $('<iframe name="formreceiver"></iframe>').appendTo(document.body).css("display", "none");
}
var form = $("#senderform");
if (!form.length) {
form = $('<form id="senderform"></form>').appendTo(document.body);
}
form = form[0]; // Raw DOM element rather than jQuery wrapper
form.target = "formreceiver";
form.action = "/Admin/Admin/ExportToCsv/?fileContents=" + csv + "&fileName=NoteTypes.csv";
form.method = "POST";
form.submit();
});
The server side is just a standard form response with the Content-Disposition header.
I've used this technique for years, with browsers from IE6 onward. My usual setup already has the iframe in the markup (rather than creating it as above), but other than that this is basically what I do.
Note that it's important you're doing this in response to a button click by the user. If you weren't, there would be popup-blocker issues.
You can't save binary files using ajax - it's restricted due to security reasons. If you'd like the user to save the file - return a binary stream from your controller as you post the data back in JSON format (ie if the user wants to save it).
I've done a similar thing here: How to properly create and download a dynamically generated binary *.xlsx file on .net MVC4?
Maybe save it as a json file instead and load in the current page's DOM. Although I though I am confused, don't you just have a JSON response containing a URL to your CSV file is which is just that a CSV file, not a JSON representation of CSV?
$("#btnExport").click(function (event) {
event.preventDefault();
var csv = table2csv(noteTypeTable, "full", "Table.dataTable", "noteTypes");
$.ajax({
url: "/Admin/Admin/ExportToCsv/?fileContents=" + csv + "&fileName=NoteTypes.json",
type: 'Post',
success: function (result) {
var myDummyElement = $('<div>'); //dummy div so you can call load
myDummyElement .load('result.url #myJson');
}
});
});
<div id="myJson"></div>
[HttpPost]
public ActionResult ExportToCsv(string fileContents, string fileName)
{
//change fileName to be a .json extension
fileContents = fileContents.Replace("-CARRIAGE-", "\r\n");
return Json(new { url = File(Encoding.UTF8.GetBytes(fileContents), "text/csv", fileName) }); ;
}

Categories

Resources