How to display flashing message without reloading the page in Flask? - python

I'm working on a web application using Flask in Python.
I have small function in my application that calculates some values in the background and displays the result on the web page via a flashing message.
Everything is displaying and working fine but it requires page reloading to get the flashing message.
I want to display messages without reloading page.
I heard that I can do that with js, but I'm not familiar with js.
If you have any ideas or suggestion I would appreciate.
There is my code that could build a better picture of what I'm doing.
This is the renderer between my app and the main html file
{% macro render_field(field) %}
<dt> {{ field.label }}
<dd> {{ field(**kwargs)|safe }}
{% if field.errors %}
<ul class=errors>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</dd>
{% endmacro %}
This is the html file were I want to display flashing messages:
<div class="container-fluid" style="min-height:100%">
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-warning alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
{{message}}
</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>

Here's what Flask Web Development: Developing Web Applications with Python (pp. 46-48) has to say of Message Flashing:
Sometimes it is useful to give the user a status update after a request is completed. This
could be a confirmation message, a warning, or an error. A typical example is when you
submit a login form to a website with a mistake and the server responds by rendering
the login form again with a message above it that informs you that your username or
password is invalid.
Flask includes this functionality as a core feature. Example 4-6 shows how the flash()
function can be used for this purpose.
Example 4-6. hello.py: Flashed messages
#app.route('/', methods=['GET', 'POST'])
def index():
form = Nameform()
if form.validate_on_submit():
old_name = session.get('name')
if old_name is not None and old_name != form.name.data:
flash('Looks like you have changed your name!')
session['name'] = form.name.data
form.name.data = ''
return redirect(url_for('index'))
return render_template('index.html', form=form, name=session.get('name'))
form = form, name = session.get('name'))
In this example, each time a name is submitted it is compared against the name stored
in the user session, which would have been put there during a previous submission of
the same form. If the two names are different, the flash() function is invoked with a
message to be displayed on the next response sent back to the client.
Calling flash() is not enough to get messages displayed; the templates used by the
application need to render these messages. The best place to render flashed messages is
the base template, because that will enable these messages in all pages. Flask makes a
get_flashed_messages() function available to templates to retrieve the messages and
render them, as shown in Example 4-7.
Example 4-7. templates/base.html: Flash message rendering
{% block content %}
<div class="container">
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message }}
</div>
{% endfor %}
{% block page_content %}{% endblock %}
</div>
{% endblock %}
In this example, messages are rendered using Bootstrap’s alert CSS styles for warning
messages (one is shown in Figure 4-4).
Figure 4-4. Flashed message
A loop is used because there could be multiple messages queued for display, one for
each time flash() was called in the previous request cycle. Messages that are retrieved from get_flashed_messages() will not be returned the next time this function is called,
so flashed messages appear only once and are then discarded.

This is not possible via Python without reloading the page. You must do this in javascript. I suggest CSS styling with display: none and display: block. Here is an example.
1) Python Code, this should go in your app.py or flask.py file.
app.route('/flash/<message>')
def flash(message):
return render_template('flash.html', msg=message)
This will render the HTML page named flash.html. The URL passed in will also have another argument, <message> this is the message that will flash. A URL like this, localhost:80/flash/Hello%20World! will flash the message "Hello World!" on your screen.
There is also another way to pass a message in, this is will arguments. The code for that is like so.
app.route('/flash')
def flash():
message = request.args.get("msg")
return render_template("flash.html", ,msg=message)
This uses the flask's request arguments. So a URL like this, localhost:80/flash?msg=Hello%20World! will give a flashing message saying "Hello World!". If you want to use this method be sure to have the import statement, from flask import request in your import statements.
2) Html Code, this is a separate file named, flash.html in your templates folder.
<body>
<h1 id="header">{{ message }}</h1>
<script>
var heading = $("#header");
setInterval(function() {
if (heading.style.display == "block") { heading.style.display = "none"; }
else if (heading.style.display == "none") { heading.style.display = "block"; }
}, 1000);
</script>
</body>
The 1000 in the setInterval is milliseconds. So the heading will blink every 2 seconds.

You may want to consider using Toastr instead. I ran into the same roadblock with Flask's Flash feature, and Toastr is pure JS. You can use it just like a console log line in your code
toastr.info("Here's a message to briefly show to your user");

Related

Flask flash message before start long running function

I have a Flask App which starts a long function after validation a form. I added a flash message before that function but the message appears after the function has finished:
# main_app.py
app = Flask(__name__)
class NameForm(FlaskForm):
name = StringField('Email Adress', validators=[DataRequired()], default='test#gmail.com')
# some more fields
submit = SubmitField('Submit')
def run_my_long_task(name):
# do some computing
# save results to a file
# return a pandas dataframe with the results for plotting with bokeh
#app.route('/', methods=['GET', 'POST'])
def index():
form = NameForm()
if form.validate_on_submit():
name = form.name.data
# I want to display a message before the run starts
flash('Analysis started, please wait.')
# Start a time consuming analysis
run_my_long_task(name)
return render_template('index.html', form=form)
Here is my index.html which contains a container with the flash message handling:
<div class="container">
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message }}
</div>
{% endfor %}
{% block page_content %}{% endblock %}
The message shows up successfully but after the run_my_long_task() has finished. How to show up this message while the job is running?
Edit: So in general I just want to inform the user about the start of the analysis. Not necessarily need to be a flash message.
Instead of using the flash function on the server-side, you might want to use some javascript to achieve this:(this is just an example, so it might seem very ugly)
<script>
document.querySelector('DOMContentLoaded', () => {
document.querySelector("#youform").addEventListener('submit', () => {
const messageRef = document.querySelector("#flash-message");
messageRef.innerHTML = 'the message';
messageRef.styles.display = 'block';
})
});
</script>
<div class="alert alert-warning" id="flash-message" style="display: hidden;">
<button type="button" class="close" data-dismiss="alert">×</button>
</div>
I came up using Bootstrap’s JavaScript modal plugin, which is enough for my needs

How to pass variable from Python API to Django template

I have been trying to work with this Python sports data api, Sportsipy. It’s pretty simple to set up and I can save data to a var and print it to the terminal but when I add it to context go to pass it through my Django template nothing shows up.
I tried calling it on the HTML side several different ways but I still haven't been able to figure it out.
Screenshot of API endpoint doc
Under the Schedule endpoint
https://sportsreference.readthedocs.io/en/stable/ncaab.html#module-sportsipy.ncaab.boxscore
def games(request):
""" View to return games page """
team_schedule = Schedule('PURDUE')
print(team_schedule)
for game in team_schedule:
away_total_rebounds = game.boxscore.away_total_rebounds
print(away_total_rebounds)
context = {
'team_schedule': team_schedule,
'away_total_rebounds': away_total_rebounds,
}
return render(request, 'games/games.html', context)
{% for game in team_schedule %}
<div>
<h4 class="white">{{ game.boxscore.away_total_rebounds }}</h4>
</div>
{% endfor %}
I'm not sure if it is the correct answer but shouldn't your for loop have elements of the for loop? What I'm trying to say is in the template shouldn't it be like
{% for game in team_schedule %}
<div>
<h4 class="white">{{ game.boxscore.away_total_rebounds }}</h4>
</div>
{% endfor %}
EDIT: Abdul's answer added

How to render new line from python string to HTML template using FLASK?

message=""
#app.route("/", methods=["GET", "POST"])
def upload_file():
global message
if request.method == "POST":
if request.files:
data=request.files["file"]
if data.filename == "":
message="File doesn't have a name! <br>"
elif allowed_file(data.filename):
message+="File Allowed <br>"
data.save(os.path.join(app.config["FILE_UPLOAD"], data.filename))
message+="File Saved"
if(validate()):
message+="File validated! <br>"
else: message+="Failed validation <br>"
else:
message+="File extension not allowed! <br>"
return render_template("ui.html",message=message)
I'm trying to validate the file uploaded on my ui.html template using flask and I want to send a "message" string back to ui.html about the status of verification and to show it nicely I'm trying to add new line whenever a new string gets added to "message" string so that when I render it in my ui.html, new line is added where I wanted it to be.
This is how I'm rendering the "message" string in ui.html:
{% if message %}
<p>{{ message }}</p>
{% endif %}
But ui.html is not rendering <br> and it is printing it as a string on ui.html template. How can I resolve this? I have tried <br /> as well.
Also mentioned in render html strings in flask templates
flask's template engine (jinja2) assumes that input inside of {{ }} is unsafe and will not allow js or html to be rendered inside of it. The easiest way is to use safe filter in order to do such thing.
{{ message | safe }}
According to flask's documentation https://flask.palletsprojects.com/en/1.1.x/templating/ there are two other ways to control autoescaping behaviour which is either wrap the HTML string in a Markup object or disabling autoescaping altogether like this:
{% autoescape false %}
<p>autoescaping is disabled here
<p>{{ will_not_be_escaped }}
{% endautoescape %}
I tackled it with flash function provided by flask. It prints each message separately so I can add <p> in my HTML File only.
The changes made in ui.html file for rendering are:
{% for message in get_flashed_messages() %}
<p>{{ message }}</p>
{% endfor %}

Altering the default Django messages tag

By default, messages.success outputs class='success'. Either I need to overwrite this, remove it or append my tags to it to meet my needs. I've been unsuccessful in finding a way to overwrite and append to it. Here I've attempted to use extra_tags...
views.py
messages.success(request, 'Item Saved', extra_tags='html_safe alert alert-')
detail.html
I've tried adding alert before the {{ message.tags }}.
{% if messages %}
<ul class='messages'>
{% for message in messages %}
<li{% if message.tags %} class='{{ message.tags }}' role='alert'{% endif %}>{% if 'html_safe' in message.tags %}{{ message|safe }}{% else %}{{ message }}{% endif %}</li>
{% endfor %}
</ul>
{% endif %}
Bootstrap Alerts expects class='alert alert-success'
<div class="alert alert-success" role="alert">
<strong>Well done!</strong> You successfully read this important alert message.
</div>
HTML page source result
When all the code above is executed, the source code outputs the following. The only problem now is the space between alert- and success.
<ul class='messages'>
<li class='html_safe alert alert- success' role='alert'>Item Saved</li>
</ul>
End Goal! - Can anyone see a hacky or proper workaround here?
<li class="html_safe alert alert-success" role="alert">Item Saved</li>
I don't see why there is a reason to remove it. Unused CSS classes are not a terrible thing. Change it to:
messages.success(request, 'Item Saved', extra_tags='html_safe alert alert-success')
and don't worry about the extra success class. Have you checked Django's source code for this? Sometimes it gives hints as to extra kwargs that can be passed. The end result will be:
<li class="html_safe alert alert-success success" role="alert">Item Saved</li>
It seems there are also ways to configure the default message tags.
For example, in your settings.py:
from django.contrib.messages import constants as messages
MESSAGE_TAGS = {
messages.SUCCESS: 'alert alert-success',
}
But some Django projects and documentation may assume that the tag here is success, the default, so only change this if you are confident it is what you need. Otherwise, just add the Bootstrap class as an extra tag. Really, you shouldn't override Django's default just to suit Bootstrap. Either change Bootstrap's success class during customization to success, or just use both.

How to flashing a message with link using Flask flash?

I'm creating a web app using Flask to deal with GoogleOpenID, these codes are almost finished, except the flashing message contains a link:
#oid.after_login
def create_or_login(resp):
user = db_session.query(User).filter_by(email=resp.email).first()
if user is not None:
flash('Successfully signed in', 'success')
else:
user = User(nickname=resp.fullname, source=GOOGLE, email=resp.email)
db_session.add(user)
db_session.commit()
flash(flashing_message, 'success')
g.user = user
session['nickname'] = user.nickname
return redirect(oid.get_next_url())
It works well when flashing_message is like this: 'Successfully registered, please click here'
But when flashing_message is 'Successfully registered, please click here', it doesn't work (flashes nothing) without throwing any Error. Strangely, sentences between flash() and return doesn't work either (did not set session['nickname] or g.user).
The other answers here focus on changing your template to allow all flash messages to be marked as safe, which may not be what you want.
If you just want to mark certain flashed messages as safe, wrap the text passed to flash() in Markup(). (Flask API Docs for Markup)
For example, instead of:
flash('Successfully registered, please click here')
Wrap the string in Markup() like this:
flash(Markup('Successfully registered, please click here'))
As always, you will need to import Markup from the flask package something like:
from flask import Markup
You need to render a template after calling flash() which should then get the message using get_flashed_messages().
You need to adjust your code to call a template after calling flash(). flash sends the message to next request which can be extracted by the template. The template can look something like:
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class=flashes>
{% for message in messages %}
<li>{{ message | safe }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
In your view code, I would add a render_template right after the flash() call. Something like:
flash('success')
return render_template('whatever.html')
Escaping HTML is the default behavior, so pass it through the safe filter to display the HTML unescaped:
{{ message|safe }}
I am not sure if it is correct, but the way I solved this was by declaring another variable in the function that was a string of HTML and then passing it through a render_template() function.
And in the template passed it through the safe filter.
For example, (roughly based on) the code you have provided:
#oid.after_login
def create_or_login(resp):
user = db_session.query(User).filter_by(email=resp.email).first()
if user is not None:
flash('Successfully signed in', 'success')
else:
user = User(nickname=resp.fullname, source=GOOGLE, email=resp.email)
db_session.add(user)
db_session.commit()
flash(flashing_message, 'success')
link = "Link" ## ADDED HTML WITH LINK ##
g.user = user
session['nickname'] = user.nickname
return render_template('some_layout.html',
link=link ## PASS THE LINK TO THE TEMPLATE ##
)
Then in the template I added an extra if statement inside the get_flashed_messages() if:
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
... Code that was already here ...
{% if link %} ## PASSED THE LINK TO THE TEMPLATE HERE ##
{{ link | safe }}
{% endif %}
{% endif %}
{% endwith %}

Categories