I'm trying to build a very basic app in Flask: a single form that accepts a few inputs from the user (I'm brand new to web development). I was able to get it up and running initially, but when I tried to modify the HTML to include Bootstrap, I get a Not a valid choice error when clicking my submit button.
Main app:
from flask import Flask, render_template, flash, session, redirect, url_for, request
from forms import CustomAnalyticsMenu
app = Flask(__name__)
app.config['SECRET_KEY'] = 'mysecretkey'
#app.route('/')
#app.route('/home')
def home():
return render_template('home.html')
#app.route('/custom_analytics_menu', methods=['GET', 'POST'])
def custom_analytics_menu():
form = CustomAnalyticsMenu()
if form.validate_on_submit():
print("validated_on_submit")
session['menu_item'] = form.menu_item.data
return redirect(url_for('submission'))
else:
print(form.errors)
return render_template('custom_analytics_menu.html', form=form)
#app.route('/submission', methods=['GET', 'POST'])
def submission():
return render_template('submission.html')
if __name__ == '__main__':
app.run(debug=True)
Form:
from flask_wtf import FlaskForm
from wtforms import StringField, RadioField, SelectField, SubmitField
from wtforms.validators import DataRequired
class CustomAnalyticsMenu(FlaskForm):
menu_item = SelectField('Menu Item:', choices=[('option_one', 'Option One'),
('option_two', 'Option Two'),
('option_three', 'Option Three')])
generate_deck = SubmitField('Generate deck')
HTML:
This is the HTML code that works:
{% extends "layout.html" %}
{% block content %}
<div>
<form method="POST" action="">
{{ form.hidden_tag() }}
{{ form.menu_item.label }} {{ form.menu_item }}
{{ form.generate_deck() }}
</form>
</div>
{% endblock %}
However, when I change this portion:
{{ form.menu_item.label }} {{ form.menu_item }}
To this: (which I got from here: https://getbootstrap.com/docs/4.0/components/forms/#form-controls)
<div class="form-group">
<label for="menu_item">{{ form.menu_item.label }}</label>
<select class="form-control" id="menu_item">
{% for item in form.menu_item.choices %}
<option>{{ item[-1] }}</option>
{% endfor %}
</select>
</div>
My SelectField functionally looks the same (except obviously it has the Bootstrap styling), but now when I click the submit button, nothing happens and I get the error Not a valid choice. This is a snippet of what I'm trying to do, I also have a RadioField and a StringField but I get the same errors for those as well when trying to apply similar styling. Can someone help me understand where I'm going wrong?
I think I figured it out! I saw another StackedOverflow question (Why is my flask form validation returning Not a valid choice?) and they had a name field in their <select>. I updated my (value, label) pairs so they matched (i.e. ('Option One', 'Option One') rather than ('option_one', 'Option One')) and updated the code to this:
<div class="form-group">
<label for="menu_item">{{ form.menu_item.label }}</label>
<select class="form-control" name="menu_item">
{% for item in form.menu_item.choices %}
<option>{{ item[-1] }}</option>
{% endfor %}
</select>
</div>
It worked! Alternatively, I could have left the (value, label) pairs the same and used the code from SuperShoot's answer above. I can't say I fully understand why the name tag was needed, so if anyone has an explanation for that, I'd love to hear it.
Edited to include name attrib on <select> field as resolution of additional issue and further explained in #RaghavPaliwal's answer.
The choices that you pass to the SelectField are a a sequence of (value, label) pairs.
In your example these are:
[('option_one', 'Option One'),
('option_two', 'Option Two'),
('option_three', 'Option Three')]
The first element of those tuples is the actual value that the option represents and is meaningful in terms of your application. It needs to be set as the value attribute of the <option> tag. The second element in each tuple is just a nicely formatted representation of the value that the option represents for display to the user.
So, you need to provide a value attribute to each of your <option> tags:
<div class="form-group">
<label for="menu_item">{{ form.menu_item.label }}</label>
<select class="form-control" id="menu_item" name="menu_item">
{% for val, text in form.menu_item.choices %}
<option value="{{ val }}">{{ text }}</option>
{% endfor %}
</select>
</div>
If you don't provide a value attribute to your <option> tags, the content of the tag will be passed as the value of the form field instead. In your case this means that if 'Option One' was selected, the string 'Option One' would be passed as the value of the field. The form validation then tests that it is included in one of the choices values, 'option_one', 'option_two', or 'option_three', so the validation fails.
Related
I am trying to write a code (CS50) that uses flask and HTML and I am supposed to create a server where you can input your name as well as a provided option. After this, the results are displayed in a table, the file is called registration.html, (using HTML) as well as recorded in a SQL database.
This is the code for app.py
from cs50 import SQL
from flask import Flask, redirect, render_template, request
app = Flask(__name__)
db = SQL("sqlite:///froshims4.db")
OPTIONS = [ "Stochastic Calculus",
"Financial Engineering",
"Statistical Sciences",
"Algorithmic Progression Systems",
"Econometrics"]
#app.route("/")
def index():
return render_template("index.html", options=OPTIONS)
#app.route("/register",methods=['POST'])
def register():
name = request.form.get("name")
option = request.form.get("option")
if not name or option not in OPTIONS:
return render_template("error.html")
db.execute("INSERT INTO registrants (name,option) VALUES (?,?)",name,option)
return redirect ('/registrants')
#Flask includes a redirect function which redirects to another route.
#app.route("/registrants")
def registrants():
registrants = db.execute("SELECT * from registrants ")
return render_template("registrants.html", registrants=registrants)
This is the code for index:
{% extends "layout.html" %}
{% block body %}
<h1>Register</h1>
<form action="/register" method="post">
#we plan to create a register route
<input autocomplete="off" autofocus name="name" placeholder="Name" type="text">
<!--a select menu is sort of like a drop down menu <select name="sport"> <option disable selected>Sport</option> -->
<!-- A radiobutton is mutually exclusive checkbox where the user can sign up for only one option-->
{% for option in options %}
<input name="option" type="checkbox" value="'{{option}}">{{option}}
{% endfor %}
</select>
<input type="submit" value="Register">
</form>
{% endblock %}
app.py directs me to error.html even if I have entered a name and a feasible option. Why is this? How can I fix it? Thank you in advance!
<input name="option" type="checkbox" value="'{{option}}">{{option}}
You have an extra single-quote inside value=. So the actual value being returned is e.g. 'Stochastic Calculus which does not match any value in the OPTIONS list.
Also you don't have a closing </option> tag.
I have been trying to figure out why my Flask form will not properly validate my select field choices even though the choices are coming from the select field options.
My assumption is that the select option when passed back from the server is unicode and is being compared to the choice which is a string, however, I thought coerce=str would fix that. I printed out the form data and request data which is the output below. Why isn't it working?
My code is attached below, removed csrf token key from the output dict. It seems like a very simple thing, but I can't figure it out.
forms.py
class PlatformForm(FlaskForm):
platform_options = [('test', 'Test'), ('test2','Test2')]
platforms = wtforms.SelectField('Platforms', choices=platform_options, coerce=str, validators=[DataRequired()])
views.py
#app.route('/', methods=['POST', 'GET'])
def index():
form = forms.PlatformForm()
if form.is_submitted():
print form.data
print request.form
if form.errors:
print form.errors
return render_template('home.html', form=form)
index.html
{% extends "base.html" %}
{% block content %}
<h4>Select a Platform</h4>
<form method="POST">
{{ form.csrf_token }}
<select class="custom-select" name="platform">
{% for value, text in form.platforms.choices %}<br>
<option value="{{ value }}">{{ text }}</option>
{% endfor %}
</select>
<button id="submit_inputs" type="submit" class="btn btn-default">Submit</button>
</form>
{% endblock %}
output
{'platforms': 'None'}
ImmutableMultiDict([('platform', u'test')])
{'platforms': [u'Not a valid choice']}
EDIT:
I figured out the problem. It's the way I'm creating the Select drop down through HTML and Jinja. Iterating through the choices and creating option tags doesn't seem to instantiate anything in the form data itself when passed back into Python. Changing that whole for loop to just
{{form.platforms}}
created a select drop down field that actually works.
You have a name mismatch. In the form, you named your select field platforms (plural). In the HTML, you use platform (singular).
I recommend that instead of manually rendering the fields in your template, you let WTForms generate the HTML for you. For the form label, you can use {{ form.platforms.label }}, and for the actual field {{ form.platforms() }}. You can pass any attributes you want to field to have as keyword arguments.
I think something might be going wrong because of the way you are rendering the form in your html file. If my hunch is right, try this:
{% extends "base.html" %}
{% block content %}
<h4>Select a Platform</h4>
<form method="POST">
{{ form.hidden_tag() }}
Select: {{ form.plaforms}}
{{ form.submit(class="btn btn-default") }}
</form>
{% endblock %}
and then try if form.validate_on_submit() in your views.py file
taken from this stack overflow answer by pjcunningham:
"validate_on_submit() is a shortcut for is_submitted() and validate().
From the source code, line 89, is_submitted() returns True if the form
submitted is an active request and the method is POST, PUT, PATCH, or
DELETE.
Generally speaking, it is used when a route can accept both GET and
POST methods and you want to validate only on a POST request."
In Materialize you define the radio group of a radio button using the attribute name, but Flask-WTForms binds the input with an attribute name.
If I have the following in my template:
{{ form.radio1(type='radio', name='group1') }}
{{ form.radio2(type='radio', name='group1') }}
There will be an error:
TypeError: html_params() got multiple values for keyword argument 'name'
And if we don't add the name, the radios won't work as radios, just as checkboxes, as expected.
How can i get around this?
This is my form class:
class AbcForm(FlaskForm):
field1 = HiddenField('Field1')
field2 = HiddenField('Field2')
and then at runtime I'll dynamically add the radios, here's a simplification:
class F(AbcForm):
pass
setattr(F, radio1, BooleanField('Radio1')
setattr(F, radio2, BooleanField('Radio2')
form = F(field1=x, field2=y)
You can't set the name attribute in your field, wtforms already does that for you.
Use the RadioField instead of HiddenField:
from wtforms import RadioField
from flask_wtf import Form
class YourForm(Form):
radio_group = RadioField('label', choices=[('value','description'),('value_two','some other description')])
(...)
Then, in your endpoint:
#route('/your/route')
def your_endpoint():
your_form = YourForm()
(...)
return render_template('/your/template.html', form=your_form)
Finally, in your template:
<form action="#">
{% for subfield in form.radio_group %}
<p>
{{ subfield }}
{{ subfield.label }}
</p>
{% endfor %}
</form>
This generates the following code:
<form action="#">
<p>
<input id="radio_group-0" name="radio_group" type="radio" value="value">
<label for="radio_group-0">description</label>
</p>
<p>
<input id="radio_group-1" name="radio_group" type="radio" value="value_two">
<label for="radio_group-1">some other description</label>
</p>
</form>
Which, given materialize is included in the template, will render the radio buttons properly as shown in this plunker.
I'm using Django and I just did a big form Using HTML5 and bootstrap. Can I still send the form via the post method to django if I'm not using it to generate the form? Should I definitely redo my form using Django?
NOTE: There may be a better way of doing this, if there is I'd really like to know, this is just how I have done it in the past.
You will still need a forms.py file in your app.
In forms.py:
from django import forms
class MyForm(forms.Form):
# FORM FIELDS HERE
Then put the form in the context dictionary for your view:
def myView(request):
if request.method == "POST":
# FORM PROCESSING HERE
else:
myform = MyForm() #create empty form
return render(request, "template.html", {"myform": myForm}
Now in your template you can add:
<form id="myForm" name="myFormName" method="post" action=".">
{% csrf_token %}
{% for field in myform %}
{{ field.as_hidden }}
{% endfor %}
</form>
This will add your django form to the page without displaying it. All of your form inputs are given the id id_fieldName where fieldName is the field name you defined in the forms.py file.
Now when the user clicks your "submit" button (which I am assuming is a bootstrap button given the rest of your form is). You can use Jquery to input the bootstrap field values into those of the hidden form.
Something like:
$("#mySubmitButton").click(function() {
$("#id_djangoFormField").val($("#myBootstrapFormField").val());
$("#myForm").submit();
}
);
This will submit the django form with the inputs from bootstrap. This can be processed in the view as normal using cleaned_data["fieldName"].
A bit late I post the solution I found for including a form in a modal in a class based detail view. Dunno if it's really orthodox but it works.
I don't use any Form Class or Model. (Django 3.9)
Within the template, I send a field value of my object in a hidden div. If this value is missing for a special action (because for the most of actions on the object, it's not required), a modal pops asking for updating the given field. This modal is triggered with JS that check the presence (or not) of the required value.
In the modal, I display a list of radio choices buttons in an ordinary form inviting the user to update the field. The form's action leads to a view that will update the given field.
modal.html
<form action="{% url 'update-sku-column' object.pk %}" method="post">
{% csrf_token %}
{% if csv_headers %}
<div class="m-3 ps-3">
{% for header in csv_headers %}
{% for csv_sample in csv_samples %}
{% if forloop.counter0 == forloop.parentloop.counter0 %}
<div class="form-check">
<input class="form-check-input" type="radio" name="chosen-field" value="{{ forloop.counter0 }}">
<label class="form-check-label" for="{{ forloop.counter0 }}">
<span class="ms-3">{{ header }} </span>: <span class="ms-1 text-secondary">{{ csv_sample }}</span>
</label>
</div>
{% endif %}
{% endfor %}
{% endfor %}
</div>
{% endif %}
<div class="modal-footer">
<button type="submit" class="btn btn-success">Enregistrer</button>
</div>
</form>
urls.py
[...]
path('flow/<int:pk>/update-sku-column',
set_sku_column, name='update-sku-column'),
[...]
views.py
#login_required
def set_sku_column(request, pk):
if request.method == 'POST':
column = request.POST['chosen-field']
flow = Flow.objects.get(pk=pk)
flow.fl_ref_index = column
flow.save()
return redirect('mappings-list', pk=pk)
[...]
Even if I can imagine it's not the best way, it works.
don't forget the {% csrf_token %}otherwise it won't
So basically I want to make a simple form I can enter text and the after I hit submit, see the text.
Here is my forms.py:
class Search(forms.Form):
search = forms.CharField()
Here is my views.py:
def search(request):
context = RequestContext(request)
if request.method == 'POST':
search = Search(data=request.POST)
if search.is_valid():
ticker = search.save()
ticker.save()
success = True
else:
print search.errors
else:
search = Search()
return render_to_response('ui/search.html', {"search":search}, context)
Here is the html form that you use to type in (I'm using bootstrap for styling purposes):
<form class="navbar-form navbar-right" role="search" action="/search/" method="post" name="tick">
{% csrf_token %}
<div class="form-group">
<input type="text" class="form-control" placeholder="Enter stock symbol">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
And finally, I want the text entered in the form to be displayed on "search.html" which looks like this currently:
{% extends 'ui/base.html' %}
{% block title %} search {% endblock %}
{% block body_block %}
<br>
<p>test</p>
{{ form.search.data }} <!--I'm pretty sure this is not correct -->
{% endblock %}
Anyone know how I can do this? Thanks.
Your form name is search.
To render the value with modern django, you need to call the value method of the field, therefore your template should look like the following:
{{ search.search.value }}
Your template is wrong, as you suspect.
It is looking for a context variable named "form", but you have given it a context dictionary with a key named "search".
Also, "data" is the argument that you use to build up your Search object (correctly), but when you want to extract the user's input from it, you should use the field names instead, and you need to call value() on them in order to get the bound value. So, to get the contents of the text field called search, you should use search.search.value.
Try changing the line
{{ form.search.data }}
to
{{ search.search.value }}