Passing variable between view and JS; Django - javascript

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

Related

Flask returning a generator and handling it in JavaScript [duplicate]

I have a view that generates data and streams it in real time. I can't figure out how to send this data to a variable that I can use in my HTML template. My current solution just outputs the data to a blank page as it arrives, which works, but I want to include it in a larger page with formatting. How do I update, format, and display the data as it is streamed to the page?
import flask
import time, math
app = flask.Flask(__name__)
#app.route('/')
def index():
def inner():
# simulate a long process to watch
for i in range(500):
j = math.sqrt(i)
time.sleep(1)
# this value should be inserted into an HTML template
yield str(i) + '<br/>\n'
return flask.Response(inner(), mimetype='text/html')
app.run(debug=True)
You can stream data in a response, but you can't dynamically update a template the way you describe. The template is rendered once on the server side, then sent to the client.
One solution is to use JavaScript to read the streamed response and output the data on the client side. Use XMLHttpRequest to make a request to the endpoint that will stream the data. Then periodically read from the stream until it's done.
This introduces complexity, but allows updating the page directly and gives complete control over what the output looks like. The following example demonstrates that by displaying both the current value and the log of all values.
This example assumes a very simple message format: a single line of data, followed by a newline. This can be as complex as needed, as long as there's a way to identify each message. For example, each loop could return a JSON object which the client decodes.
from math import sqrt
from time import sleep
from flask import Flask, render_template
app = Flask(__name__)
#app.route("/")
def index():
return render_template("index.html")
#app.route("/stream")
def stream():
def generate():
for i in range(500):
yield "{}\n".format(sqrt(i))
sleep(1)
return app.response_class(generate(), mimetype="text/plain")
<p>This is the latest output: <span id="latest"></span></p>
<p>This is all the output:</p>
<ul id="output"></ul>
<script>
var latest = document.getElementById('latest');
var output = document.getElementById('output');
var xhr = new XMLHttpRequest();
xhr.open('GET', '{{ url_for('stream') }}');
xhr.send();
var position = 0;
function handleNewData() {
// the response text include the entire response so far
// split the messages, then take the messages that haven't been handled yet
// position tracks how many messages have been handled
// messages end with a newline, so split will always show one extra empty message at the end
var messages = xhr.responseText.split('\n');
messages.slice(position, -1).forEach(function(value) {
latest.textContent = value; // update the latest value in place
// build and append a new item to a list to log all output
var item = document.createElement('li');
item.textContent = value;
output.appendChild(item);
});
position = messages.length - 1;
}
var timer;
timer = setInterval(function() {
// check the response for new data
handleNewData();
// stop checking once the response has ended
if (xhr.readyState == XMLHttpRequest.DONE) {
clearInterval(timer);
latest.textContent = 'Done';
}
}, 1000);
</script>
An <iframe> can be used to display streamed HTML output, but it has some downsides. The frame is a separate document, which increases resource usage. Since it's only displaying the streamed data, it might not be easy to style it like the rest of the page. It can only append data, so long output will render below the visible scroll area. It can't modify other parts of the page in response to each event.
index.html renders the page with a frame pointed at the stream endpoint. The frame has fairly small default dimensions, so you may want to to style it further. Use render_template_string, which knows to escape variables, to render the HTML for each item (or use render_template with a more complex template file). An initial line can be yielded to load CSS in the frame first.
from flask import render_template_string, stream_with_context
#app.route("/stream")
def stream():
#stream_with_context
def generate():
yield render_template_string('<link rel=stylesheet href="{{ url_for("static", filename="stream.css") }}">')
for i in range(500):
yield render_template_string("<p>{{ i }}: {{ s }}</p>\n", i=i, s=sqrt(i))
sleep(1)
return app.response_class(generate())
<p>This is all the output:</p>
<iframe src="{{ url_for("stream") }}"></iframe>
5 years late, but this actually can be done the way you were initially trying to do it, javascript is totally unnecessary (Edit: the author of the accepted answer added the iframe section after I wrote this). You just have to include embed the output as an <iframe>:
from flask import Flask, render_template, Response
import time, math
app = Flask(__name__)
#app.route('/content')
def content():
"""
Render the content a url different from index
"""
def inner():
# simulate a long process to watch
for i in range(500):
j = math.sqrt(i)
time.sleep(1)
# this value should be inserted into an HTML template
yield str(i) + '<br/>\n'
return Response(inner(), mimetype='text/html')
#app.route('/')
def index():
"""
Render a template at the index. The content will be embedded in this template
"""
return render_template('index.html.jinja')
app.run(debug=True)
Then the 'index.html.jinja' file will include an <iframe> with the content url as the src, which would something like:
<!doctype html>
<head>
<title>Title</title>
</head>
<body>
<div>
<iframe frameborder="0"
onresize="noresize"
style='background: transparent; width: 100%; height:100%;'
src="{{ url_for('content')}}">
</iframe>
</div>
</body>
When rendering user-provided data render_template_string() should be used to render the content to avoid injection attacks. However, I left this out of the example because it adds additional complexity, is outside the scope of the question, isn't relevant to the OP since he isn't streaming user-provided data, and won't be relevant for the vast majority of people seeing this post since streaming user-provided data is a far edge case that few if any people will ever have to do.
Originally I had a similar problem to the one posted here where a model is being trained and the update should be stationary and formatted in Html. The following answer is for future reference or people trying to solve the same problem and need inspiration.
A good solution to achieve this is to use an EventSource in Javascript, as described here. This listener can be started using a context variable, such as from a form or other source. The listener is stopped by sending a stop command. A sleep command is used for visualization without doing any real work in this example. Lastly, Html formatting can be achieved using Javascript DOM-Manipulation.
Flask Application
import flask
import time
app = flask.Flask(__name__)
#app.route('/learn')
def learn():
def update():
yield 'data: Prepare for learning\n\n'
# Preapre model
time.sleep(1.0)
for i in range(1, 101):
# Perform update
time.sleep(0.1)
yield f'data: {i}%\n\n'
yield 'data: close\n\n'
return flask.Response(update(), mimetype='text/event-stream')
#app.route('/', methods=['GET', 'POST'])
def index():
train_model = False
if flask.request.method == 'POST':
if 'train_model' in list(flask.request.form):
train_model = True
return flask.render_template('index.html', train_model=train_model)
app.run(threaded=True)
HTML Template
<form action="/" method="post">
<input name="train_model" type="submit" value="Train Model" />
</form>
<p id="learn_output"></p>
{% if train_model %}
<script>
var target_output = document.getElementById("learn_output");
var learn_update = new EventSource("/learn");
learn_update.onmessage = function (e) {
if (e.data == "close") {
learn_update.close();
} else {
target_output.innerHTML = "Status: " + e.data;
}
};
</script>
{% endif %}

Refresh a django subtemplate with javascript - reload only part of the page

I'm trying to refresh a subtemplate by calling a view with js. Nothing seems to happen, though the server gets hit and seems to return a response.
The goal is to refresh the included part without refreshing the whole page.
Minimal example:
views.py
def ajax_view(request):
context = {}
context['data'] = 'data'
return render(request, "test.html", context)// <-- reaches here, but no rendering
test.html
{{data}}
main.html
<script type="text/javascript">
function getQuery() {
var request = new XMLHttpRequest(),
method = 'GET',
url = '/ajax/';
request.open(method, url);
request.send();
}
</script>
{% include "test.html" %} // <-- does not update
With the help of a friend, I solved this.
I'm going to answer my question with some detail, because I am 100% sure someone is going to need to figure out how to do this later. Happy to save you the trouble.
I had to change my mental model about how this works.
I still send a request to the server with javascript, and the view returns the templated HTML.
However, and this is the important part, what I do is take the rendered HTML (from the response, what is returned with return render(response, "your_template.html", context)) and replace that part of my page with that HTML.
Here's the Javascript.
function getQuery(item) {
// Sends a request to the API endpoint to fetch data
let request = new XMLHttpRequest();
let method = 'GET';
let query = // omitted
let url = '/ajax/?' + query;
request.open(method, url);
request.onload = function () {
// the response is the rendered HTML
// which django sends as return render(response, "your_template.html", context)
let myHTML = request.response;
// This is the important part
// Set that HTML to the new, templated HTML returned by the server
document.getElementById('flex-container-A').innerHTML = myHTML;
};
request.send();
}
and for completeness, here's the view:
views.py
def ajax_view(request):
"""
Server API endpoint for fetching data
"""
context = {}
queryset = Form.objects
if request.method == 'GET':
query = request.GET.get('q')
# do some stuff and get some data
context['data'] = data
return render(request, "my_template.html", context)
Hope this helps you! This is a common pattern but not often mentioned so explicitly.

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.

Django - Ajax without JQuery lib

I am Learning and using Ajax without jQuery Lib. I created a simple view which renders a random number on to the template. I added a button and called the ajax function. However, clicking the button doesn't change the value on the screen. I checked in DOM (firebug) and it shows the someText.responseText is yielding the whole html rather than just the value, however it has the new value in that HTML. I am sharing this to provide more information on what I have found so far. I am new to this and experimented it with a lot for ex; I have checked "request.is_ajax():" but somehow the view does not find ajax in request. I have printed request on command line and the GET querydict is empty.
Obviously I am not doing something in the correct manner for Django. Please assist.
I have a view;
def home(request):
rand_num = random.randint(1,100)
return render(request, 'home.html', {'rand_num':rand_num})
and html and script;
<html>
<head>
<script type='text/javascript'>
var someText;
function helloWorld(){
someText = new XMLHttpRequest();
someText.onreadystatechange = callBack;
someText.open("GET", "{% url 'home' %}", true);
someText.send();
};
// When information comes back from the server.
function callBack(){
if(someText.readyState==4 && someText.status==200){
document.getElementById('result').innerHtml = someText.responseText;
}
};
</script>
</head>
<body>
<div id="result">{{rand_num}}</div>
<input type='button' value='Abraca dabra!' onclick="helloWorld()"/>
</body>
</html>
here is the url for this view;
url(r'^$', 'socialauth.views.home', name='home'),
I am learning this from an online tutorial.
That is because your AJAX endpoint is the whole view - I.e. your AJAX request asks for the whole rendered template.
What you want is just a number, so make a new view and URL to return just the number, and make the AJAX request to that.
AJAX isn't anything special, its just a way to make an asynchronous request to a URL and get the contents returned.
For reference, a view using something like JSONResponse:
from django.http import JsonResponse
def get_random_number_json(request):
random_no = random.randint(1,100)
return JsonResponse({'random_no': random_no})
Then in your frontend Javascript, fetch url for that view, which will give you only the JSON in your javascript variable via your AJAX call, and instead of all that document.getElementById('result') processing, you can just grab the variable from the json object.

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