Call function with arguments from user input in Python3 Flask Jinja2 template - python

I am writing a web application which accepts user input in an HTML form written in Jinja2.
Based on the input, I set variables defined in Jinja2 which I then want to pass as positional arguments to another function defined in either my __init__.py file or main.py file.
Python version==3.6.8
Flask==2.0.1
Jinja2==3.0.1
I attempted many ways to achieve this but could not so (got an undefined error when trying to call the function), including following the suggestions in this thread: Call a python function from jinja2
The file structure of my web app looks like so:
https://prntscr.com/1rlt3cl (I can't post images because I don't have enough reputation points so I uploaded it here).
I am receiving user input in my form.html file which I pass to the data.html file.
<form action="/data" method = "POST">
<p>Various inputs<input type = "number" name = "random" /></p>
<p><input type = "submit" value = "Submit" /></p>
</form>
From the data.html file, I want to accept the values inputted by the user, and call a function which sits in all of my __init__.py files (wasn't sure which one Flask actually looks at):
{% set ns1 = namespace(random=None) %}
{% for key,value in form_data.items() %}
{% if key == "iterations" %}
{%- set ns1.iterations = value -%}
{% endif %}
{% endfor %}
{{ clever_function(random) }}
clever_function is defined in the __init__.py file as so:
from jinja2 import Template
def clever_function():
return "Hello"
template = Template("{{ clever_function() }}")
template.globals['clever_function'] = clever_function
When running my application from webapp/main.py which renders the form / data templates like so:
#app.route('/form')
def form():
return render_template('form.html')
#app.route('/data', methods = ['POST', 'GET'])
def data():
if request.method == 'GET':
return f"The URL /data is accessed directly. Try going to '/form' to submit form"
if request.method == 'POST':
form_data = request.form
return render_template('data.html',form_data = form_data)
I receive the following exception:
jinja2.exceptions.UndefinedError: 'clever_function' is undefined

The code you use is a brief description of the exclusive use within a directly defined template.
To use a custom function within a template used by render_template, you have to add it to the dictionary globals of the jinja environment.
from flask import Flask
from flask import render_template, request
def clever_function(value):
return value**2
app = Flask(__name__)
app.jinja_env.globals.update(clever_function=clever_function)
#app.route('/form')
def form():
return render_template('form.html')
#app.route('/data', methods=['POST'])
def data():
return render_template('data.html', form_data=request.form)
From now on, this function can be called in every template which is loaded and called up with this environment.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<p>Result: {{ clever_function(form_data.get('random', 0) | int) }}</p>
</body>
</html>
Please keep in mind that the defined function must accept parameters if you want to call it up with any.
Errors have also crept in when using the namespace object. You should take another look at the documentation for the assignments in jinja2.
Here is a small example based on your code.
{% set ns1 = namespace(random=0) %}
{% for k,v in form_data.items() %}
{% if k == 'random' %}
{% set ns1.random = v | int %}
{% endif %}
{% endfor %}
<p>Value is {{ns1.random}}</p>

Related

Accessing flask methods in Jinaj2.Template.render()

Say we have a flask template as such:
{% extends "layout.html" %}
{% block body %}
<div class="container page-container">
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class=flashes>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
</div>
{% endblock %}
We can render this template using the flask.render_template() function, and thus display a flash message using code like this:
from flask import flash, render_template
#timesheet_billing.route('/timesheet-billing')
def timesheet_billing_select_job():
jobs = get_open_jobs()
flash('A job was not selected!')
return render_template('timesheet_billing/index.html', jobs = jobs)
However if we render it using Jinja2's template class function jinja2.Template.render() with code like this:
from flask import flash
import jinja2
env = jinja2.Environment(loader=jinja2.PackageLoader('templates'))
index_temp = env.get_template('index.html')
#timesheet_billing.route('/timesheet-billing')
def timesheet_billing_select_job():
jobs = get_open_jobs()
flash('A job was not selected!')
return index_temp.render(jobs = jobs)
We get the following error when trying to load the page:
jinja2.exceptions.UndefinedError: 'get_flashed_messages' is undefined
What is the difference here? The answer in this question suggests that they should be the same. However it seems in one we do not have access to flask methods.
I believe the difference here is how flask.get_flashed_messages works.
This webpage https://flask.palletsprojects.com/en/1.1.x/templating/ explains the scope of jinja2 context variables:
The Jinja Context Behavior:
These variables are added to the context of variables, they are not global variables. The difference is that by default these will not show up in the context of imported templates.
This is partially caused by performance considerations, partially to
keep things explicit.
Here is where flask's render_template makes a difference, when comparing it to jinja2.render (from the question link you referred to):
def render_template(template_name_or_list, **context):
ctx = _app_ctx_stack.top
ctx.app.update_template_context(context)
return _render(ctx.app.jinja_env.get_or_select_template(template_name_or_list),
context, ctx.app)
def _render(template, context, app):
before_render_template.send(app, template=template, context=context)
rv = template.render(context)
template_rendered.send(app, template=template, context=context)
return rv
by calling render directly, you're missing the application context update call, which will inject all the information a template context processor will need to make functions (like get_flashed_messages, in this case) available to templates.

How to handle a variable in HTML?

I have the following flask application that displays a dashboard with various buttons. Each button executes a python function. After the execution of such a function I want the application to return to the dashboard. In order to give the user a simple log I want to output some string on the html page. For that thought about a tag above the buttons on the dashboard that get filled with the respective value. How can I do that?
Flask:
from flask import Flask, render_template
app = Flask(__name__)
#app.route("/")
def main():
return render_template('index.html')
#app.route('/something')
def do_something():
print("Hello")
return render_template('index.html', user="Successfully executed!")
if __name__ == "__main__":
app.run()
HTML:
<!DOCTYPE html>
<html>
<head>
<head>
<meta charset="UTF-8">
</head>
<title>MP Reporting</title>
</head>
<body>
<div value=user></div>
Your button
</body>
</html>
For flask template use "{{kwarg}}" i.e. in your example
<div>{{user}}</div>
will render as
<div>Successfully executed!</div>
In addition to other answers, I suggest using Flask's built-in message flashing which is simpler, and neater instead of passing variables to render_template manually. It's simple as that:
(template)
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div>{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
(flask view)
from flask import flash
flash("Successfully executed!")
You can get more information from here.
You can print variables using Jinja2.
To print out the variable user in your example add
{{ user }} in the html template.
If you send a list of items to the html you can output them by using a simple for:
{% for item in items %}
{{ item }}
{% endfor %}

Python Flask cannot get element from form

Im having trouble getting anything from the shown HTML form
I always get "ValueError: View function did not return a response"
Can somebody help me out here please? I have tried every variation of request.get that I can find on the web. Also if I specify my form should use post it uses get anyway - anybody know why this is?
Im new to flask so forgive my ignorance!
Thanks in advance.
The python file (routes.py)
from flask import Flask, render_template, request
import os
app = Flask(__name__)
musicpath = os.listdir(r"C:\Users\Oscar\Music\iTunes\iTunes Media\Music")
lsize = str(len(musicpath))
looper = len(musicpath)
#app.route('/')
def home():
return render_template('home.html', lsize=20, looper=looper, musicpath=musicpath)
#app.route('/pop', methods=['POST', 'GET'])
def pop():
if request.method == "GET":
text = request.args.get('som')
return text
#Have tried every variation of request.get
#app.route('/about')
def about():
name = "Hello!"
return render_template('about.html', name=name)
if __name__ == '__main__':
app.run(debug=True)
The html file (home.html)
{% extends "layout.html" %}
{% block content %}
<div class="jumbo">
<h2>A Music app!<h2>
</div>
<div>
{% if lsize %}
<form action="/pop">
<select id="som" size="20">
{% for i in range(looper):%}
<option value="{{i}}">{{ musicpath[i] }}</option>
{% endfor %}
</select>
</form>
{% endif %}
</div>
Select,
{% endblock %}
You don't have a name attribute on your select element. That is the attribute that browsers use to send information in forms; without it no data will be sent.
Note also that your pop handler does not do anything if the method is POST, even though you explicitly say you accept that method.

Python Flask get item from HTML select form

Im having trouble getting anything from the shown HTML form
I always get "ValueError: View function did not return a response"
Can somebody help me out here please? I have tried every variation of request.get that I can find on the web. Also if I specify my form should use post it uses get anyway - anybody know why this is?
Im new to flask so forgive my ignorance!
Thanks in advance.
The python file (routes.py)
from flask import Flask, render_template, request
import os
app = Flask(__name__)
musicpath = os.listdir(r"C:\Users\Oscar\Music\iTunes\iTunes Media\Music")
lsize = str(len(musicpath))
looper = len(musicpath)
#app.route('/')
def home():
return render_template('home.html', lsize=20, looper=looper, musicpath=musicpath)
#app.route('/pop', methods=['POST', 'GET'])
def pop():
if request.method == "GET":
text = request.args.get('som')
return text
#Have tried every variation of request.get
#app.route('/about')
def about():
name = "Hello!"
return render_template('about.html', name=name)
if __name__ == '__main__':
app.run(debug=True)
The html file (home.html)
{% extends "layout.html" %}
{% block content %}
<div class="jumbo">
<h2>A Music app!<h2>
</div>
<div>
{% if lsize %}
<form action="/pop">
<select id="som" size="20">
{% for i in range(looper):%}
<option value="{{i}}">{{ musicpath[i] }}</option>
{% endfor %}
</select>
</form>
{% endif %}
</div>
Select,
{% endblock %}
The Problem is that your HTML form does not have a name.
request.args.get("som") needs an HTML form input with the name "som"
<select name="som" id="som" size="20">
Just change that line and add a name. The form is not interested in the id attribute.
You don't specified the method of the form, you have to do it! For example use this<form method="POST action"/pop">
Your form action is /pop. That means that if you submit the form it will do a POST request to the address /pop. Your code does only return a value for a GET request, therefore Flask complains you do not return anything. Write some code to process a POST request and return a text or rendered template.
BTW, in the code for GET you refer to request.args.get('som'); this gives you request arguments (i.e. in the URL), not from the form. som is in the form, so you cannot refer to it this way.

value error if cookie value is a string but not if an int

In my make_session function, if the passwords from the forms don't match some credentials, then it returns a string which ends up being stored as a cookie named session. In my index file, I have it set so on index.html, if the cookie value is {}, it asks for login information. If I change what is returned from the make_session function by replacing "invalid" with any integer, the code works as expected.
A bad solution to my problem is to add
except:
data = {}
before the return to my get_saved_data function setting data equal to {} but this ends up with the same results as if I had no cookie in my browser at all, but it gets rid of my error which is here: https://gist.github.com/anonymous/e101aa46f154a075b038
I suspect that the get_saved_data function may be a fault.
Map of my directory:
|---- layout.html
|---- index.html
|--- templates -|
Project -|
|--- test.py
test.py:
from flask import Flask, render_template, redirect, url_for, request, make_response
import json
def get_saved_data(key):
try:
data = json.loads(request.cookies.get(key))
except TypeError:
data = {}
return data
def make_session(form_data):
if form_data.get('username') == "username" and form_data.get('password') == "password":
return "12345"
else:
return "invalid"
app = Flask(__name__)
#app.route('/')
def index():
data = get_saved_data("session")
return render_template('index.html', saves=data)
#app.route('/login', methods=['POST'])
def login():
response = make_response(redirect(url_for('index')))
response.set_cookie("session", make_session(dict(request.form.items())))
return response
app.run(debug=True, host='0.0.0.0', port=8000)
index.html:
{% extends "layout.html" %}
{% block content %}
{% if saves == {}: %}
<p>Please log in.</p>
{% else: %}
<p>Your Session value is: {{ saves }}</p>
{% endif %}
{% if saves == {}: %}
<form action="{{ url_for('login') }}" method="POST">
<p>We take your private information very seriously. All data is encrypted not once but twice! in ROT13 to provide the best security.</p><br />
<label for="username">Please enter your username:</label>
<input type="text" name="username" /><br />
<label for="password">Please enter your password:</label>
<input type="text" name="password" /><br />
<button class="btn">Log In</button>
</form>
{% endif %}
{% endblock %}
layout.html:
<!DOCTYPE html>
<html>
<head>
<title>Character Generator</title>
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
Change your get_saved_data to the following will make any difference?
def get_saved_data(key):
data = json.loads(request.cookies.get(key, '{}'))
return data
return '{}' if there is no such key, make sure json won't complain anything.
While I haven't found a way to directly fix the issue, and using lord63.j's answer saved me a few lines of code, a way to indirectly fix the issue is to save the cookie value as a dictionary by modifying the login function to this:
#app.route('/login', methods=['POST'])
def login():
response = make_response(redirect(url_for('index')))
response.set_cookie("data", json.dumps({"session": make_session(dict(request.form.items()))}))
return response
from them on, in order to access the session value I'll need to use
saves.get('session')
as saves is equivalent to
json.loads(request.cookies.get('data', '{}'))
in the index function
#app.route('/')
def index():
data = get_saved_data("data")
return render_template('index.html', saves=data)

Categories