Python Flask, custom status of background process as response - javascript

I'm having trouble returning a custom response from a background process that takes a long time and its being called from a view function in Flask. I use the EventSource method from the html to display the status of the process to the user. The process is encapsulated in an object instanced in the view function and I need a way to signalize the status of the process.
The significant parts of the object are:
class MyObject:
self.status = 0
def signal_status(self):
yield self.status
def doSomeProcess(self):
self.status = 1
self.foo()
self.status = 2
self.bar()
self.status = 3
def process_call(self):
Thread(target=self.async_process, args=(app, data)).start()
def async_process(self, app, data):
with app.app_context():
self.doSomeProcess(data)
And from the routes:
myObj = MyObject()
#app.route('/process/<data>')
def process(data):
global myObject
myObject.process_call(data)
return render_template('process.html')
#app.route('/progress')
def progress():
global myObject
return Response(myObject.signal_status(), mimetype='text/event-stream')
Then I'm trying to reveive the code in js from the response like this:
<script>
var source = new EventSource("/progress");
source.onmessage = function(event) {
console.log(event.data);
}
</script>
However nothing appears in the console more than a 200 response message. I've tried casting to str the yield response from signal_status(). If I print the call to signal_status() the number is displayed normaly, so i don't think the threading is the issue.
Am I using the right approach to this issue, or Is there any other way I could display the status of the process in the client?

Related

How to package plain JSON content in a WSGI response?

I have a Django WSGI (not my decision) website making a call to fetch dynamically generated JavaScript. I put the function in views.py and it's receiving the request and doing the work, but the return value is being rejected.
The HTML (JavaScript section of web page) that calls this function does it like this:
var jscript = document.createElement('script');
jscript.id = 'generate';
jscript.style.visibility = 'hidden';
jscript.style.display = 'none';
jscript.src = `/generate?callback=catchOptions${query}`; // jsonp https://en.wikipedia.org/wiki/JSONP query is a list of parameters in query string format
if (document.getElementById("generate") == null)
document.body.appendChild(jscript); // javascript needs this to work properly
There's map file that maps /generate to /generate_planet (see below). Getting into the function works great. It's the return value that Djangoff is rejecting.
Here is the function in views.py
from cgitb import reset
from django.shortcuts import render
from . import planetor
from django.http import JsonResponse
def generate_planet(request):
res = planetor.generate(request.content_params, "/app/planetor/", "FRAMES=1")
# res is JSON text, NOT a python dict
return res
# res looks like this:`callback({'camera_location': '-30,-30,-30', 'camera_angle': '30', 'sun_color': '5,5,5', 'sun_position': '10000,0,-10000', 'planet_size': '20.06', 'background': 'background_2.jpg', 'planet': 'surface_1.jpg', 'clouds_size': '1.02', 'clouds': 'clouds_16.jpg', 'clouds_density': '0.80', 'atmosphere': 'iodine', 'atmosphere_density': '0.95', 'atmosphere_size': '1.03', 'moons': '4', 'moon_position': None, 'moon_size': None, 'moon': None, 'random_color': None, 'random_float': None, 'random_trans': None, 'star_system': 'Barnard', 'star_index': 'Zeta', 'planet_index': 'II', 'planet_type': 'Surface ', 'identity': '81654447928', 'designation': 'v_star_index v_star_system v_planet_index', 'clouds_file': 'clouds_16.jpg'})
The function call actually works, and the "planetor.generate()" runs. The problem is, the return JSON (JSONP really) from this, is rejected by Djangoff
Djangoff spits out this:
Internal Server Error: /generate_planet
Traceback (most recent call last):
File "/usr/local/lib/python3.9/dist-packages/django/core/handlers/exception.py", line 47, in inner
response = get_response(request)
File "/usr/local/lib/python3.9/dist-packages/django/utils/deprecation.py", line 119, in __call__
response = self.process_response(request, response)
File "/usr/local/lib/python3.9/dist-packages/django/middleware/clickjacking.py", line 33, in process_response
response.headers['X-Frame-Options'] = self.get_xframe_options_value(
AttributeError: 'dict' object has no attribute 'headers'
[05/Jun/2022 16:52:11] "GET /generate_planet? HTTP/1.1" 500 56694
It's looking for the return value to be wrapped in something I'm sure but for the life of my I can't find 1) any API documents for WSGIResponse to I can construct one and 2) examples of anyone doing anything like this with Djangoff
I eventually figured this out.
If you send a request to Django, like this:
/my_request?key1=value1&key2=value2&key3=value3
By whatever means (raw URL, form submit, ajax request, whatever)
To have Django catch that request and return a JSON answer put a function like this in views.py
def my_request(request):
selections = request.GET # <== this gets the query string paramaters as a dictionary
# use the query string parameters as the parameters of the function for creating the answer to the request
res = {"answer1":"value1","answer2":"value2"} # << python dictionary of answer
return JsonResponse(res) # convert dictionary to JSON
If you want to get JSONP back, you'll have to just code the raw javascript:
return 'callback({"answer1":"value1","answer2":"value2"})'

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).

JSON Response output is not updated on single click event

I am trying to build a java online compiler web app using django.
but every time I submit my code through AJAX call the new code is posted successfully to view.py but the JSON response object is still the previous one.
def code1(request):
response_data={}
**codet=request.POST.get("code","")** # getting the code text from Ajax req
**output=code(codet)** #sending code for execution
response_data['result']="Successful" #storing in response
response_data['message']=output
**return HttpResponse(json.dumps(response_data), content_type="application/json")**
def code(code_text):
return execute(code_text)
def execute(code_text):
filename_code=open('Main.java','w+') #creating file
filename_code.flush()
f1 = code_text.split("\n") # splitting code line by line
for i in f1:
filename_code.write(i+"\n") #writing code line by line
filename_code.close() #closing file
time.sleep(2) #giving wait
**compile_java(filename_code)**
#compiling file (Note: I am not using the file passed in compile_java)
**return execute_java(filename_code,input**) #running java file
def compile_java(java_file):
proc = subprocess.Popen('javac Main.java', shell=True)
def execute_java(java_file, stdin):
#java_class,ext = os.path.splitext(java_file)
cmd = ['java ', 'Main']
proc = subprocess.Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
stdout,stderr = proc.communicate(stdin)
stdoutstr= str(stdout,'utf-8')
**
1. return stdoutstr
**
Actually the previous json response is being sent for current code(codet)

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.

Categories

Resources