How to inject a variable into <img src='VARIABLE'> - python

If I place one of the dictionary values(i.e "https://....) directly into <img src, the image will render properly. Using the variable 'musician'(which contains "https://...) will yield an empty properly formatted frame with a broken image logo.
musician_dict = {
'g': 'https://media.stubhubstatic.com/stubhub-catalog/d_defaultLogo.jpg/t_f-fs-0fv,q_auto:low,f_auto,c_fill,$w_280_mul_3,$h_180_mul_3/performer/404807/gvwionkcazusra2wbkuu',
'p': 'https://cdn.britannica.com/18/161418-050-811E4CBE/McCoy-Tyner.jpg?w=400&h=300&c=crop'
}
# The website starts with a main page
#app.route("/")
def piano_or_guitar_game():
musician = random.choice(list(musician_dict.values()))
return "<h1>pIANO player or gUITAR player(q to quit)</h1>" \
"<img src=musician width=500>"

You're sending it back as a string literal. With Flask, you'd usually do something like:
return render_template("index.html", picture=musician)
and then in your templates folder, you would have:
<img src={{picture}} width=500>
See Flask's blog tutorial.

Related

Python yield clear output [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 %}

Replacing text from string with an image using flask

I have written a piece of code that matches if the string has anything of the pattern text.someExtension, In my case, it would be fileName.png in the string, converts it into an img tag and displays on the HTML file using python and flask. Let us take the example string:
"What is the output of this program? e.png"
the code matches e.png and it then replaces e.png by
"<br><img src="{{url_for('static', filename='pics/e.png')}}" /><br>"
The image e.png is put in the folder pics inside the static folder.
If this string is pushed into a flask variable even by adding Markup() to it it isn't rendering the image but showing the following output.
output on html page
why is it so? Any way to make it display the image e.png?
my code is:
import re
from flask import Flask, Markup, render_template
app = Flask(__name__)
def rep(x):
text = re.findall(r'\w+[.]\w+', x)
for i in text:
b ="<img src=\"{{ url_for('static',filename='pics/"+i+"')}}\">"
x=x.replace(i,b)
return x
#app.route('/')
def home():
a = "What is the output of this program? e.png"
a = rep(a)
return render_template('new.html',var=Markup(a))
if __name__ == '__main__':
app.run(debug=True, host='localhost', port=8032)
And the HTML file is,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{var}}
</body>
</html>
The problem is that the value you're passing to your template is a string and even though the string you're inserting is formatted with the {{ brackets, flask doesn't interpret those. Notice if you look at the html being served, it actually contains the string 'url_for'...
You're also not even importing the url_for function from flask, so that wouldn't have worked anyway.
The solution:
import re
from flask import Flask, url_for, Markup, render_template
app = Flask(__name__)
def rep(x):
text = re.findall(r'\w+[.]\w+', x)
for i in text:
b ="<img src='" + url_for('static', filename='pics/'+i) + "'>"
x=x.replace(i,b)
return x
#app.route('/')
def home():
a = "What is the output of this program? e.png"
a = rep(a)
return render_template('new.html', var=Markup(a))
#app.route('/static/<path:path>')
def static_file(path):
return send_from_directory('static', path)
if __name__ == '__main__':
app.run(debug=True, host='localhost', port=8032)
The html file can remain unchanged. This solution
separately tells the flask server to listen and serve the static files under the /static page.
Creates the html with the image url by string concatenation, instead of trying to use template rendering.

How to send images in a jinja html template?

So I've got a setup of flask + jinja and I'm trying to display an image in an html file being rendered by jinja.
In Python I'm converting it into base64 and then sending it to the template. I'm then using an image tag to display the image.
<img src="data:image/jpeg;base64,{{ myimage }}">
I've confirmed that the Python encoding is correct, it displays as it should when I simply write an html file with the base64 embedded into it. Where it seems to fail is from the template modifying the output a little bit. In particular:
<img src=3D"data:;base64,/9j/4QAYR
...
baW4WqWj/2Q=3D=3D"/>
Jinja seems to be screwing around by adding the text 3D in a couple places where it looks like it shouldn't be. I haven't specified anything differently, and when I printed out myimage just as text, it came up the way I expected it to, starting with /9j and ending with /2Q==
I'm not sure if there's something with the way I'm interpreting it in Jijna or what, but it just doesn't load. I see the image src tag in the email source, but there's just nothing where I expect the image to be loaded.
Markup the variable myimage as safe:
<img src="data:image/jpeg;base64,{{ myimage | safe }}">
Simple single file app (uses requests library):
from flask import Flask, render_template_string
import base64
import requests
app = Flask(__name__)
global _base64_encoded_image
#app.route('/')
def index():
_html_template = '''
<p><img src="data:image/jpeg;base64,{{ myimage | safe }}"><p>
<p><img src="data:image/jpeg;base64,{{ myimage | e }}"><p>
<p><img src="data:image/jpeg;base64,{{ myimage }}"><p>
'''
global _base64_encoded_image
return render_template_string(_html_template, myimage=_base64_encoded_image)
#app.before_first_request
def before_first_request():
global _base64_encoded_image
_url = "http://via.placeholder.com/200?text=Flask/Jinja2"
_r = requests.get(_url)
_base64_encoded_image = base64.b64encode(_r.content)
print _base64_encoded_image
if __name__ == '__main__':
app.run()
This looks like a URL encoding problem as = is a character used in that base64 alphabet and its URL encoding is %3D. Try doing a urllib.quote on the base64 data before rendering it with Jinja.

Python webapp to set all query vars to a js object

I am building a simple web app with Python and web.py. My current challenge is to take all query vars (web.input()) and set a JavaScript object in the HTML template. However, I don't know how to ensure this gets rendered OK instead of a long string with encoding.
Thanks a million!
I have the following code in app.py, template HTML and content HTML:
app.py:
import web
urls = (
'/hello', 'hello',
'/bye/', 'bye'
)
app = web.application(urls, globals(), autoreload=True)
#test = "testwaarde"
test = web.input()
render = web.template.render('templates/', base='layout')
class hello:
def GET(self):
return render.index("Templates demo",test, "Hello", "A long time ago...")
class bye:
def GET(self):
return render.hell_form("Templates demo",test, "Bye", "14", "8", "25", "42", "19")
if __name__ == "__main__":
app.run()
Template:
$def with (page)
<html>
<head>
<title>$page.title</title>
<!-- Digital Data Layer -->
<script>
var test = "{{$page.path}}"
var digitalData = {
'page':{
'path': '$page.path'
}
};
</script>
</head>
<body>
<p>You are visiting page <b>$page.name</b>.</p>
$:page
</body>
</html>
index HTML:
$def with (title, **path,name, content)
$var title:$title
$var name:$name
$var path:$path
<p>$content</p>
You're close:
Move your call to test = web.input() to within your GET() methods, not at the top of the file. That's just a bug. (I know that's just your sample code, but it doesn't work.)
Within index.html, use
$var path = path
The using var path: $path sets the template variable to a string representation of path. You want to set it to the dict. That's one difference between the colon and the equal sign. Second, because it's '=', the right hand side is interpreted as python, so no leading '$'.
Within your template layout.html, change your javascript to something similar to:
var test = "$:page.path";
var digitalData = {
'page': {
'path': $:page.path
}
};
You'll want to escape the data using $: rather than $. That's why you're seeing the encoded values. Also, you don't want to surround the final $:page.path with quotes to set page.path to an object, rather than just a string.

Dynamically update image using Python Flask AJAX

I have 1 very simple web application I am building right now but am very new to flask and jinja (and web development as a whole actually).
I have a watch folder, which will be getting an image sent to it via ftp on a pulse for ever. This wtch folder will only ever have one image in. Every 1 minute, the old image is replaced by a new image, with a new timestamp.
I would like to dynamically update the page, (and displayed timestamp) on a pulse as well, without having to reload any banners or static images that I will add later. I only want to update the following two lines out of the "Channels.Jinja" sample to follow.
<br>{{screenshot_datetime}}<br/>
<img src={{screenshot_location}} width="100%"/>
Channels.Jinja
<!DOCTYPE HTML>
<html>
<head>
<title>Training</title>
</head>
<body bgcolor=white>
<div id=main>
<br>Date and Time of Screenshot <br/>
<br>{{screenshot_datetime}}<br/>
<img src={{screenshot_location}} width="100%"/>
</div>
<div id='test'>
<p>
<script>
var myVar=setInterval(function(){get_image()},1000);
function get_image() {
$.ajax({
type: 'GET',
cache: false,
url: 'get_data',
success: function({{data}}) {
$('img').attr('src', data);
}
});
}
</script>
</p>
</div>
</body>
</html>
Channels.py
def render_channel_route(cr):
static_folder = os.path.join('static',cr)
file_list = os.listdir(static_folder)
channel_files = [f for f in file_list if f.startswith(cr)]
if not channel_files :
logger.error('Could not find image file for Channel. File should start with {0}'.format(cr))
abort(404)
img = os.path.join(static_folder,file_list[0])
ts = get_time_from_filename(file_list[0],cr)
return render_template('Channels.jinja',screenshot_datetime=time.strftime('%c',ts),screenshot_location=img)
#app.route('/channel01-10')
def first_tab():
return render_channel_route('channel01-10')
#app.route('/get_data', methods=['GET'])
def get_data():
return render_template('Channels.jinja',
screenshot_datetime=time.strftime('%c',ts),screenshot_location=img)
Im at a loss, Ive been bumbling around for a while now. Any and all advice is welcome! I am seeing a 304 response upon refresh, but not even the timer i am trying to put on it is working. Pardon sloppy code, highly volatile code is getting changed often -_-
I don't know it there is a "special" way to deal with Ajax using some Flask extension, but in the "normal" Ajax flow first you need to use url_for to put the correct url in your Ajax call and return the data formatted in some way (in my example in JSON) and not to render the template again:
$.ajax({
type: 'GET',
cache: false,
url: "{{ url_for('get_data') }}",
success: function(resp){
$('img').attr('src', resp.url);
$('#sst').html(resp.time);
}
});
So, in your get_data function in your controller you have to get the time and the path again for your image an then return some like this (to fit in my example before):
from flask import json
#app.route('/get_data', methods=['GET'])
def get_data():
#get time and path
time=...
path=...
return json.dumps({time:time,url:path}), 200, {'Content-Type':'application/json'}
Look that I use $('#sst') so you have to put in your HTML:
<br><span id='sst'>{{screenshot_datetime}}</span><br/>

Categories