Flask passing request.form value to url_for - python

I have a Flask template that displays a page with a dropdown list of owners, a table with the owner's win-loss record, and a radio to toggle between regular season record and playoff record.
The desired workflow is:
If navigated to the page through the navigation bar, it should default to /matchup-history/regular. (this works)
Else it should route accordingly whenever the radio is toggled. (this doesn't work)
matchup-history.html
{%- extends "base.html" -%}
{% block nav_matchups %}active{% endblock %}
{%- block content -%}
<form action="{{ url_for('show_matchup_history', matchup_type=request.form['matchup_type']) }}" method="post">
<label>
<select name="owner_id" onchange="this.form.submit()">
{%- for o in owners %}
{%- if request.form['owner_id'] == o['owner_id']|string() %}
<option value="{{ o['owner_id'] }}" selected>{{o['first_name'] + " " + o['last_name'] }}</option>
{%- else %}
<option value="{{ o['owner_id'] }}">{{o['first_name'] + " " + o['last_name'] }}</option>
{%- endif %}
{%- endfor %}
</select>
</label>
{% block matchup_type_radio %}{% endblock %}
</form>
{%- if records|length > 0 %}
<div class="stats-table">
<table>
<tr>
{%- for th in table_headers %}
<th>{{ th }}</th>
{%- endfor %}
</tr>
{%- for r in records %}
<tr>
{%- for cn in column_names %}
<td>{{ r[cn] }}</td>
{%- endfor %}
</tr>
{%- endfor %}
</table>
</div>
{%- endif %}
{% endblock -%}
matchup-history/regular.html
{%- extends "matchup-history.html" -%}
{% block matchup_type_radio %}
<label><input type="radio" name="matchup_type" value="regular" onclick="this.form.submit()" checked>Regular Season</label>
<label><input type="radio" name="matchup_type" value="playoffs" onclick="this.form.submit()">Playoffs</label>
{% endblock %}
matchup-history/playoffs.html
{%- extends "matchup-history.html" -%}
{% block matchup_type_radio %}
<label><input type="radio" name="matchup_type" value="regular" onclick="this.form.submit()">Regular Season</label>
<label><input type="radio" name="matchup_type" value="playoffs" onclick="this.form.submit()" checked>Playoffs</label>
{% endblock %}
app.py
#app.route('/matchup-history/<string:matchup_type>', methods=['GET', 'POST'])
def show_matchup_history(matchup_type):
table_headers = ["Opponent", "Wins", "Losses"]
column_names = ["opponent_owner_name", "wins", "losses"]
owners = queries.get_owners()
if request.method == 'POST':
owner_id = request.form['owner_id']
else:
owner_id = owners[0]['owner_id']
if matchup_type == REGULAR_SEASON:
records = queries.get_matchup_history_regular(owner_id)
else:
records = queries.get_matchup_history_playoffs(owner_id)
return render_template("matchup-history/{matchup_type}.html".format(matchup_type=matchup_type),
title='Matchup History', table_headers=table_headers, column_names=column_names,
owners=owners, records=records)
The page correctly loads /matchup-history/regular when clicked on, but fails whenever the radio button is toggled:
127.0.0.1 - - [20/Sep/2018 08:32:53] "GET /matchup-history/regular HTTP/1.1" 200 -
127.0.0.1 - - [20/Sep/2018 08:32:56] "POST /matchup-history/ HTTP/1.1" 404 -
It seems like request.form['matchup_type'] is empty when matchup-history.html is rendered, so submitting the form will not have the desired effect. How can I refactor to route url_for to different matchup_type?
Edit: Per #Joost's suggestion, I rethought the design.
matchup-history.html
{%- extends "base.html" -%}
{% block nav_matchups %}active{% endblock %}
{%- block content -%}
<form action="{{ url_for('show_matchup_history') }}" method="get">
<label>
<select name="owner_id" onchange="this.form.submit()">
{%- for o in owners %}
<option value="{{ o['owner_id'] }}" {%- if o['owner_id'] == selected_owner %} selected {% endif %}>{{o['first_name'] + " " + o['last_name'] }}</option>
{%- endfor %}
</select>
</label>
<label><input type="radio" name="matchup_type" value="regular" onclick="this.form.submit()" {%- if matchup_type == "regular" %} checked {% endif %}>Regular Season</label>
<label><input type="radio" name="matchup_type" value="playoffs" onclick="this.form.submit()"{%- if matchup_type == "playoffs" %} checked {% endif %}>Playoffs</label>
</form>
{%- if records|length > 0 %}
<div class="stats-table">
<table>
<tr>
{%- for th in table_headers %}
<th>{{ th }}</th>
{%- endfor %}
</tr>
{%- for r in records %}
<tr>
{%- for cn in column_names %}
<td>{{ r[cn] }}</td>
{%- endfor %}
</tr>
{%- endfor %}
</table>
</div>
{%- endif %}
{% endblock -%}
base.html
...
Matchups
...
app.py
#app.route('/matchup-history', methods=['GET'])
def show_matchup_history():
table_headers = ["Opponent", "Wins", "Losses"]
column_names = ["opponent_owner_name", "wins", "losses"]
matchup_type = request.args.get('matchup_type', default="regular")
owner_id = request.args.get('owner_id', type=int)
owners = queries.get_owners()
if not owner_id:
owner_id = owners[0]['owner_id']
if matchup_type == REGULAR_SEASON:
records = queries.get_matchup_history_regular(owner_id)
else:
records = queries.get_matchup_history_playoffs(owner_id)
return render_template("matchup-history.html".format(matchup_type=matchup_type),
title='Matchup History', table_headers=table_headers, column_names=column_names,
matchup_type=matchup_type, selected_owner=owner_id, owners=owners, records=records)
Flow is now:
Clicking on Matchups from navbar will route to /matchup-history and default to showing regular season matchups
Clicking on Playoffs radio will route to /matchup-history?matchup_type=playoffs&owner_id=12345
Clicking on Regular radio will route to /matchup-history?matchup_type=regular&owner_id=12345
Clicking on a different owner in the dropdown will route to /matchup-history?matchup_type=regular&owner_id=98765

So right now you are trying to access the request.form in a get request. However, the form will always be empty in a get request because that's the nature of a get request. So only when you access the route #app.route('/matchup-history/<string:matchup_type>' trough a post request, will it be able to redirect in the right way.
This working miniapp displays it nicely:
from flask import Flask, render_template_string, request
app = Flask(__name__)
TEMPLATE_STRING = """
<form action="{{ url_for('index') }}" method="post">
{{request.form['matchup_type']}}<br><br>
<label><input type="radio" name="matchup_type" value="regular" onclick="this.form.submit()" checked>Regular Season</label>
<label><input type="radio" name="matchup_type" value="playoffs" onclick="this.form.submit()">Playoffs</label>
</form>
"""
#app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
return render_template_string(TEMPLATE_STRING)
else:
return render_template_string(TEMPLATE_STRING)
The first time you open the page, you see only the radio button. But as soon as you click on a radio button, it POSTS the form, and therefore you will see the selected value on top of the page now. If you click again, you post the form again, etc.
So how should you solve it? I think that there is no need to do a POST request with this form, because you're not updating any data, you're just querying.
from flask import Flask, render_template_string, request
app = Flask(__name__)
TEMPLATE_STRING = """
<form action="{{ url_for('history') }}" method="get">
<select name="owner_id">
{% for owner in owners %}
<option {% if owner['id'] == selected_owner_id %} selected {% endif %}value="{{owner['id']}}">{{owner['name']}}</option>
{% endfor %}
</select>
<label><input type="radio" name="matchup_type" value="regular" {%if selected_matchup_type == 'regular'%}checked{%endif%} onclick="this.form.submit()">Regular Season</label>
<label><input type="radio" name="matchup_type" value="playoffs" {%if selected_matchup_type == 'playoffs'%}checked{%endif%} onclick="this.form.submit()" >Playoffs</label>
<br>Queried data goes here
</form>
"""
owners = [{'id': 1, 'name': 'bob'}, {'id': 2, 'name': 'gary'}, {'id': 3, 'name': 'tom'}]
matchup_types = 'regular', 'playoffs'
#app.route('/history', methods=['GET'])
def history():
owner_id = request.args.get('owner_id', None, type=int)
if owner_id not in [owner['id'] for owner in owners]:
owner_id = owners[0]['id']
matchup_type = request.args.get('matchup_type', None)
if matchup_type not in matchup_types:
matchup_type = matchup_types[0]
# now you know the owner_id and the matchup type, and know that both are valid, do some query to get table data
return render_template_string(TEMPLATE_STRING, owners=owners,
selected_owner_id=owner_id,
selected_matchup_type=matchup_type,
matchup_types=matchup_types)
I think this is what you need. The form is never posted, by always put as a get request (<form action="{{ url_for('history') }}" method="get">). If the values are missing or invalid, we default back to some owner/matchup_type. The checked values are remembered, and used to render the template.
This puts all you flask logic in the #app.route, and all your jinja logic in the template.
Some general remarks:
I think that accessing the request in jinja not preferable because jinja deals with errors/missing values differently, and if they are a result of logic related to your request, it's becomes hard to guess what's going on. So deal with the incoming request on the python side.
Instead of wrapping having 2 radio blocks depending on the chosen value, just use one block and check within the options if the are to one you need. <option {% if some_value == some_other_value %} checked {% endif%}>blabla</option>.
Do a lot more input validation! In your first example, you template name is decided by some user inputted value (the matchup type). But what if the user posts a nonexisting value? You get errors.
If the only difference between the two templates is which radio button is selected, you don't need two templates. See the updated version how to just deal with it in one template.

Related

select entries from database mysql in dropdown, python flask

I want to select data from database table in MySQL. It is procedure in Python
#app.route('/formworkers.html')
def formworkers():
cursor = mysql.connection.cursor()
cur = cursor.execute("SELECT ID_worker, surname FROM workers")
return render_template('formworkers.html', workers0=cursor.fetchall())
And template in Jinja2
{% extends "layout.html" %}
{% block body %}
{% if session.logged_in %}
<form action="" method=post class=add-worker >
<dl>
<dt>Surname:
<dd><select name="worker0">
{% for ID_pats, surname in workers0 %}
<option value="{{ ID_worker }}">{{ surname }}</option>
{% endfor %}
</select>
<br><br>
<dd><input type=submit value="Submit">
</dl>
</form>
{% endif %}
{% endblock %}
But the dropdown list does not contain data (for example [Ivanov, Petrov, Sidorov]) from the database, it contain values [surname, surname, surname]. Thank you.
The query outputs a result as a list of dictionaries, so when you unpack it in the for loop, you get only a list of keys "ID_worker" and "surname" .
You can change the template like below to access the values :
{% for worker in workers0 %}
<option value="{{ worker.ID_worker }}">{{ worker.surname }}</option>
{% endfor %}

Django ifequal and if Statement Always Going to Else tags

I am currently trying to compare the product id to the id given in the URL. But the if statement in the template always returns "else" even though testing provides both to be equal.
views.py (where data is given)
def editstatus(request, product_id):
try:
request.session['user_id']
except KeyError:
return redirect("/login")
products = Product.objects.all()
context = {
"product":products,
"theid" : product_id,
}
return render(request, 'posystem/status.html', context)
status.html (with not working if statement)
{%for product in product%}
<tbody>
<tr>
<td>{{product.id}}</td>
<td>{{theid}}</td>
<td>{{product.product_description}}</td>
<td>{{product.product_number}}</td>
<td>{{product.product_quantity}}</td>
<td>{{product.unit_cost}}</td>
<td>{{product.final_cost}}</td>
<td>{{product.status}}</td>
{% ifequal product.id theid %}
<h1>hello</h1>
{% else %}
<h1>hello2</h1>
{% endifequal %}
{% if theid %}
{% if product.id == theid %}
<td><select>
<option value="5 Votes Needed">5 Votes Needed</option>
<option value="Ready to Order">Ready to Order</option>
<option value="Needs to Be Signed">Needs to Be Signed</option>
<option value="Ordered">Ordered</option>
<option value="Recieved">Recieved</option>
</select></td>
<td><form class="" action="/changestatus/{{product.id}}" method="post">
{% csrf_token %}
<button type="submit" name="edit">Save</button>
</form></td>
{% endif %}
{% else %}
<td><form class="" action="/status/{{product.id}}" method="post">
{% csrf_token %}
<button type="submit" name="edit">Edit</button>
</form></td>
{% endif %}
</tr>
</tbody>
{% endfor %}
I am confused on why it will neither work with a ifequal tag nor a normal if tag.
Since product_id is from the URL then it will be a string, not an integer. You need to convert it to an integer.
context = {
"product":products,
"theid" : int(product_id),
}
In Python, and the Django template language, '1' is not equal to 1.

'dict' object has no attribute 'all_aircraft'

I have a list of posts that the user is able to favorite and save to their to their account. However, I keep getting the error message above. Does anybody have any idea as to where the issue lies?
views.py:
def AircraftFavourite(request, id=None):
instance = get_object_or_404(Aircraft, id=id)
queryset = Aircraft.objects.all()
context = {'all_aircraft' : queryset}
try:
selected_aircraft = context.all_aircraft.get(pk=request.POST['context'])
except(KeyError, Aircraft.DoesNotExist):
return render(request,'aircraft.html', {
"aircraft" : instance,
"error_message" : "You did not select a valid aircraft",
})
else:
selected_aircraft.is_favorite = True
selected_aircraft.save()
return render(request,'aircraft.html', context)
urls.py
urlpatterns = [
url(r'^detail/(?P<id>\d+)/$', AircraftDetail, name='AircraftDetail'),
url(r'^(?P<id>\d+)/favourite/$', AircraftFavourite, name='AircraftFavourite'),]
aircraft.html
{% block content %}
{% for a in all_aircraft %}
<table>
<tr>
<th> {{ a.title }}
</th>
</tr>
<tr>
<td>
<form action="{% url 'AircraftFavourite' id=a.id %}" method="post">
{% csrf_token %}
<input type="submit" id="aircraft{{ forloop.counter }}" name ="aircraft" value="{{ a.id }}">
<label for="aircraft{{ forloop.counter }}"
{% if aircraft.is_favourite %}
<img src="http://i.imgur.com/b9b13Rd.png" />
{% endif %}
<input type="submit" value="Favourite"><br>
</form>
</td>
</tr>
</table>
{% endfor %}
{% endblock %}
In django templates attribute access and item access are equivalent:
template.html
#rendered with context = dict(a=dict(something=5), b=SomeObject))
<body>
{{ a.something }} vs {{ b.something }}
</body>
Both will work and django figures out whether to access the attribute or get the item.
However in Python, the attribute access with the . is not equivalent to item access with the brackets [ ].
Your script should be changed to reflect this:
try:
selected_aircraft = context['all_aircraft'].get(pk=request.POST['context'])
Or ideally just access the queryset directly, so the code better reflects what you're doing:
try:
selected_aircraft = queryset.get(pk=request.POST['context'])
Change to this:
selected_aircraft = get_object_or_404(Aircraft, id=request.POST['context'])

Flask Select field - Not a valid choice

I have the following code in form.py
OPTIONS = ['Option1', 'Option2', 'Option3']
class Test_Form(Form):
test = SelectField('Dropdown', coerce= str,
choices=[(f, f) for f in OPTIONS])
submit = SubmitField('Submit')
And the following code in my template
<div class = "control-group">
<label class="control-label">
{{ form.test.label }} </label>
{% if form.test.errors %}
{% for error in form.test.errors %}
<p class="error-message"> {{ error }}</p>
{% endfor %}
{% endif %}
<div class="controls">
<select name=form.test.label width="80px">
{% for i,j in form.test.choices %}
<option value = {{ i }} > {{ j }} </option>
{% endfor %}
</select>
</div>
</div>
Following is my view function
def show_logs():
my_form = Test_Form()
if request.method == "POST":
if logs_form.validate() == False:
return render_template('test.html', form = my_form)
else:
return my_form.test.data
#return render_template('test_success.html', output = output_list)
elif request.method == "GET":
return render_template('test.html', form = my_form)
I get a "Not a Valid Choice" every time I submit the form. I went through the previous questions on SO and tried coerce = str but I still get the same message. What am I missing here?
I tried your code and it works perfectly on my end. The only thing I changed however was your template code as yours was incomplete. It had no submit button and no <form> tag declaration.
This is the template code that I used:
<form action="" method='post'>
{{ form.hidden_tag() }}
<ul class="request_form">
{%if form.errors%}
Please correct the following fields:
{%for each in form.errors%}
<br>{{each}}
{%endfor%}
{%endif%}
{%for each in form%}
{%if each.name != "csrf_token" and each.name!="submit"%}
<li>
<label>{{each.name}}</label>
{{each()}}
</li>
{%endif%}
{%endfor%}
<li/>{{form.submit}}
</ul>
</form>
Also, in your view, you're checking for logs_form.validate() when it should be my_form.validate()
I hope this solves your problem.

how to submit checklist in Django with GET

Hi I have on my html file this, tags and tagInfos are both queryset´s of Django, that 2 for´s are only to show wich one belongs to the other:
<form action="/chart/chart/" method="get">
{% if tags.count > 0 %}
{% for tag in tags %}
{% for tagInfo in tagInfos %}
{% if tag.taginfo_idtaginfo1_id == tagInfo.idtaginfo %}
<p>
<input type="checkbox" name="checks[]" value="{{ tag.idtag }}" />
</p>
{% endif %}
{% endfor %}
{% endfor %}
{% csrf_token %}
<button type="submit" class="btn btn-primary">Submit creation</button>
{% else %}
<p>None TAG to select.</p>
{% endif %}
</form>
So and on my view i try to do that:
def chart(request):
if 'checks' in request.GET and request.GET['checks']:
chosen = request.GET.getlist('checks')
return render_to_response('Chart/chart.html',{'chosen':chosen})
else:
return render_to_response('Chart/chart.html',{})
But don´t show any of the selected checkboxes on the other html, I´m using {{ chosen }} to show.
Any ideas of what I´m doing wrong?
try this:
def chart(request):
if 'checks[]' in request.GET: #<----- 'checks[]'
chosen = request.GET.getlist('checks[]') #<----- 'checks[]'
return render_to_response('Chart/chart.html',{'chosen':chosen})
else:
return render_to_response('Chart/chart.html',{})

Categories