please tell me how to access min_entries correctly, I need it to generate fields for the form.
My codes:
forms.py:
class ToSend(FlaskForm):
send = FieldList(FormField(AddEquipment), min_entries=())
equipment_add.py:
#app.route('/equipment_add', methods=['GET', 'POST'])
def addEquipment():
update = 0
if request.method == "POST":
update = int(request.form['update'])
print(update)
form = ToSend()
form.send.min_entries = update
return render_template('equipment_add.html', form=form, update=update)
return render_template('equipment_add.html', update=update)
And so I turn form.send.min_entries = update and there's a mistake:
while len(self.entries) < self.min_entries:
TypeError: '<' not supported between instances of 'int' and 'tuple'
What am I doing wrong?
The problem arises because you didn't specify an integer as the value for the minimum number when defining the FieldList, but a pair of brackets.
If you don't enter a value here, the default value of 0 will be used automatically.
As I understand your code, you want to dynamically add form fields depending on the number requested by another form.
You can solve your problem in different ways. I'll show you two options, using a GET request to query how many fields the form should contain.
The first option is to add the fields manually using append_entry().
from flask import (
Flask,
render_template,
request
)
from flask_wtf import FlaskForm, Form
from wtforms import FieldList, FormField, StringField, SubmitField
app = Flask(__name__)
app.secret_key = 'your secret here'
class EquipmentForm(Form):
name = StringField('Name')
class AddEquipmentForm(FlaskForm):
items = FieldList(FormField(EquipmentForm))
submit = SubmitField('Add Items')
#app.route('/equipment/add', methods=['GET', 'POST'])
def equipment_add():
num_fields = max(0, request.args.get('cnt', 0, type=int))
form = AddEquipmentForm(request.form)
form.items.min_entries = num_fields
while len(form.items.entries) < form.items.min_entries:
form.items.append_entry()
if form.validate_on_submit():
print(form.items.data)
return render_template('equipment_add.html', **locals())
The second option uses a factory to recreate the form for each request and sets the min_entries attribute of the FieldList.
from flask import (
Flask,
render_template,
request
)
from flask_wtf import FlaskForm, Form
from wtforms import FieldList, FormField, StringField, SubmitField
app = Flask(__name__)
app.secret_key = 'your secret here'
class EquipmentForm(Form):
name = StringField('Name')
class AddEquipmentForm(FlaskForm):
submit = SubmitField('Add Items')
#app.route('/equipment/add', methods=['GET', 'POST'])
def equipment_add():
def _factory(num_items):
class F(AddEquipmentForm): pass
setattr(F, 'items', FieldList(FormField(EquipmentForm), min_entries=num_items))
return F
num_fields = max(0, request.args.get('cnt', 0, type=int))
form = _factory(num_fields)(request.form)
if form.validate_on_submit():
print(form.items.data)
return render_template('equipment_add.html', **locals())
The template would then look like this for both examples.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
</head>
<body>
<form>
<label for="cnt">Add Item Count</label>
<input type="number" name="cnt" value="{{ num_fields }}" min="0" />
<input type="submit" />
</form>
<hr/>
{% if form.items | count > 0 -%}
<form method='post'>
{{ form.csrf_token }}
{% for subform in form.items -%}
{% for field in subform -%}
<div class="">
{{ field.label() }}
{{ field() }}
{% if field.errors -%}
<ul>
{% for error in field.errors -%}
<li>{{ error }}</li>
{% endfor -%}
</ul>
{% endif -%}
</div>
{% endfor -%}
{% endfor -%}
{{ form.submit() }}
</form>
{% endif -%}
</body>
</html>
Related
I'm trying to send a client side value to the server when a HTMX request is made. I can't seem to get the value using request.args.get('cardCount')or request.form.get('cardCount').
I'm using flask_htmx to catch HTMX requests server side and render partial html. I need to get the cardCount from the dom so I can increment the new card being added correctly. If I don't add 'cards-3-front' and 'cards-3-back' to the html id and name the wtforms FieldList will not save dynamically added textarea content to the db.
Here is the form:
# NOTECARD FORMS
class CardForm(Form):
front = TextAreaField('front', validators=[InputRequired()])
back = TextAreaField('back', validators=[InputRequired()])
class NoteForm(FlaskForm):
title = StringField('Title', validators=[InputRequired(), Length(max=35)])
cards = FieldList(FormField(CardForm), min_entries=3)
Here is the view:
#views.route('/create', methods=['GET', 'POST'])
#login_required
def create():
user_img = get_user_img()
form = NoteForm()
if htmx:
count = request.args.get('cardCount')
return render_template("./partials/notecard.html", count=count)
if request.method == 'POST':
if form.validate_on_submit():
fronts = [bleach.clean(card.front.data) for card in form.cards]
backs = [bleach.clean(card.back.data) for card in form.cards]
set = dict(zip(fronts, backs))
db.session.add(Notes(title=bleach.clean(form.title.data), content=set, user_id=current_user.id))
db.session.commit()
flash('New Notecard Set Created!', 'success')
return redirect(url_for('views.dashboard'))
else:
flash('All notecard fields must be filled in', 'warning')
return render_template("create.html", user_img=user_img, form=form)
Here is the htmx request I'm trying to send from the template:
<div name="cardCount">3</div>
<button hx-get="/create" hx-include="[name='cardCount']" hx-target="#newCards" hx-swap="beforeend" class="btn"></button>
It seems to me that your approach using hx-include fails due to the fact that you want to include the content of a div element and not the value of an input element.
Below is an example where you can add and remove form fields using htmx. The entire form is replaced by an htmx call.
I use an input field of type hidden to determine the number of input fields.
To determine whether fields should be added or removed, an additional header is used using hx-headers.
Depending on the data transmitted via htmx, the form is changed and replaced as a whole, with the entries made being retained.
Flask (app.py)
from flask import (
Flask,
redirect,
render_template,
request,
url_for
)
from flask_htmx import HTMX
from flask_wtf import FlaskForm, Form
from wtforms import (
FieldList,
FormField,
StringField,
TextAreaField
)
from wtforms.validators import (
DataRequired,
Length
)
app = Flask(__name__)
app.secret_key = 'your secret here'
htmx = HTMX(app)
class CardForm(Form):
front = TextAreaField('Front', validators=[DataRequired()])
back = TextAreaField('Back', validators=[DataRequired()])
class NoteForm(FlaskForm):
title = StringField('Title', validators=[DataRequired(), Length(min=3, max=35)])
cards = FieldList(FormField(CardForm), min_entries=1)
#app.route('/', methods=['GET', 'POST'])
def index():
form = NoteForm(request.form)
num_fields = max(0, request.form.get('count', 1, type=int))
if htmx:
form.validate()
hx_func = request.headers.get('Hx-Func', 'DEL')
if hx_func.lower() == 'del':
num_fields = max(1, num_fields - 1)
form.cards.min_entries = num_fields
form.cards.entries = form.cards.entries[:form.cards.min_entries]
else:
num_fields += 1
form.cards.min_entries = num_fields
while len(form.cards.entries) < form.cards.min_entries:
form.cards.append_entry()
return render_template('partials/form.html', **locals())
else:
if form.validate_on_submit():
print(form.title.data, form.cards.data)
# Use the form data here.
return render_template('index.html', **locals())
HTML Template (index.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Index</title>
</head>
<body>
{% include 'partials/form.html' %}
<script src="https://unpkg.com/htmx.org#1.8.5"></script>
</body>
</html>
HTML Template (partials/form.html)
<form name="my-form" method="post">
{{ form.csrf_token }}
<div>
{{ form.title.label() }}
{{ form.title() }}
{% if form.title.errors -%}
<ul>
{% for error in form.title.errors -%}
<li>{{ error }}</li>
{% endfor -%}
</ul>
{% endif -%}
</div>
{% for subform in form.cards -%}
{% for field in subform -%}
<div>
{{ field.label() }}
{{ field() }}
{% if field.errors -%}
<ul>
{% for error in field.errors -%}
<li>{{ error }}</li>
{% endfor -%}
</ul>
{% endif -%}
</div>
{% endfor -%}
{% endfor -%}
<input type="hidden" name="count" value="{{ num_fields }}" />
<button
type="button"
hx-post="/"
hx-target="[name='my-form']"
hx-headers='{"Hx-Func": "DEL"}'
hx-swap="outerHTML"
>Less</button>
<button
type="button"
hx-post="/"
hx-target="[name='my-form']"
hx-headers='{"Hx-Func": "ADD"}'
hx-swap="outerHTML"
>More</button>
<button type="submit">Submit</button>
</form>
I'm creating a flask/python/html project that asks the user for their name. I'm trying to use wtforms to validate the input.
This is the html (mainScraper.html):
{% extends 'base.html' %}
{% block head %}
<link rel ="stylesheet" href = "mainScraper.css">
{% endblock %}
{% block body %}
<h1> This is the main page </h1>
<p> Please enter your name: </p>
<br/>
<form action = "mainScraper", method = "POST">
{{ form.csrf_token }}
{{ form.username.label }}
{{ form.username }}
{{ form.submit }}
</form>
{% endblock %}
And this is the flask/python:
from flask import Flask, render_template, request, redirect, url_for, session, flash
from flask_wtf import FlaskForm
from wtforms import SubmitField, StringField
from wtforms.validators import data_required, length
app = Flask(__name__)
app.secret_key = "34Secret56"
#app.route('/')
def index():
return render_template('mainScraper.html')
name = ' '
class NameForm(FlaskForm):
username = StringField('Name', validators = [data_required(),length(min = 1)])
submit = SubmitField('Submit')
#app.route('/mainScraper', methods = ['POST', 'GET'])
def mainScraper():
form = NameForm()
if form.validate_on_submit():
global name
name = form.username.data
return render_template('general.html')
return render_template('mainScraper.html', form=form)
I want to make sure at least one character is submitted before moving on to the next html page (from the mainScraper.html to the general.html).
But I'm getting this error from my mainScraper.html file: "jinja2.exceptions.UndefinedError: 'form' is undefined" and I'm confused as to why, because I defined 'form' in the corresponding method of the python file, mainScraper. And I’m not sure how else I should validate my forms in html because most online videos or websites I’ve seen use flask-WTF or WTForms with {{ form.csrf_token}} in a html file
Also I’m not using an IDE for the python file, but React for the html file, is that a problem?
If so, what IDE do you suggest I use for the python and html file?
For some reason, form.validate_on_submit() does not return anything.
from flask import Flask
from flask_wtf import FlaskForm
from wtforms import StringField, DecimalField, validators
from flask import render_template
app = Flask(__name__)
app.config.update(dict(
SECRET_KEY="super awesome key"
))
class MyForm(FlaskForm):
name = StringField('Product name', [validators.InputRequired(), validators.Length(min=0, max=30)])
#app.route('/', methods=['GET', 'POST'])
def index():
form = MyForm()
if form.validate_on_submit():
return "Mission accomplished!"
return render_template('submit.html', form=form)
{% 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 %}
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Add new grocery product</h1>
<p>Provide appropriate product details</p>
<form method="POST" action="/">
{{ form.csrf_token }}
{{ render_field(form.name.label) }} {{ form.name(size=20) }}<br>
<input type="submit" value="Go">
</form>
</body>
</html>
The app itself is supposed to gather user input in specific form and insert this via SQLAlchemy into database. Output is supposed to:
Return "Mission accomplished!" if validation succeeded
Return validation of which field failed and display error msg "This field is required" on the submit.html template
EDIT
Duo some success with the code, I make another edit to the question. It seems that macro isn't displaying error message for appropriate field if the validation fails.
I.E. if Field name is empty, macro should create and display error message ( This Field cannot be empty) by itself.
FINAL EDIT
I managed to find the solution. Submit.html template seems to cause the issue with macro if render_field looks like this:
{{ render_field(form.name.label) }}
instaed of this:
{{ render_field(form.name) }}
#This might help
from flask import Flask
from flask_wtf import FlaskForm
#from wtform import SubmitField
from wtforms import StringField, DecimalField, validators,SubmitField
from flask import render_template
app = Flask(__name__)
app.config.update(dict(
SECRET_KEY="super awesome key"
))
class MyForm(FlaskForm):
you need to make an edit to your validators like this
name = StringField('Product name', validators = [InputRequired(), , Length(min=0, max=30)])
#create submit option in your flask form
submit = SubmitField("Submit")
#app.route('/', methods=['GET', 'POST'])
def index():
form = MyForm()
if form.validate_on_submit():
return "Mission accomplished!"
return render_template('submit.html', form=form)
Hi I am a bit confused about a flask application. I have a route / to welcomepage that generates a ux_uuid then passes it to the form. Now, after I submit the form I want the ID (ux_uuid) passed on as well. However, in all cases (form.validate==false or even if all is ok) it ends up in a 405 error - Method not allowed. The methods=['GET','POST'] has the ['POST'] method is there so I don't understand why even if request.method == 'POST' breaks?
app.py:
import logging, os
from flask import Flask, json, make_response, render_template, request, session, redirect, url_for, jsonify
from ux.forms import QuestionForm
from flask_wtf.csrf import CSRFProtect
import sqlite3
app = Flask(__name__, static_url_path='')
app.secret_key = "NSA secret key"
csrf = CSRFProtect()
csrf.init_app(app)
#app.route('/', methods=['GET', 'POST'])
def welcomepage():
# Here is the random user ID generated by some random function but
# can be set to some string to test:
ux_uuid = 'TETST_ID' #or set this to ux_uuid='TestUserID'
return render_template('welcomepage.html', ux_uuid=ux_uuid)
#app.route('/form1/<ux_uuid>', methods=['GET', 'POST'])
def form1(ux_uuid):
form = QuestionForm()
if request.method == 'POST':
if form.validate() == False:
print("VALIDATE ME !")
return render_template('/form1.html/<ux_uuid>', form=form, ux_uuid=ux_uuid)
else:
#
#mylist = json.dumps(form.first_question)
print('I STORED ALL THE FORM DATA IN THE SQLITE3 DB here - and GO ON TO:')
return render_template('signup.html', ux_uuid=ux_uuid)
elif request.method == 'GET':
return render_template('form1.html', form=form, ux_uuid=ux_uuid)
return render_template('form1.html')
# turn on debug=true to see error messages
if __name__ == "__main__":
app.run(debug=True)
This is the /ux/forms.py:
from flask_wtf import Form
from wtforms import SelectMultipleField, SubmitField, RadioField
from wtforms.validators import DataRequired, Email, Length
class QuestionForm(Form):
first_question = SelectMultipleField('What is your favourite food? Select all that apply:', choices=[('pizza','pizza'), ('more pizza', 'more pizza'), ('burger', 'burger'), ('salad', 'salad'), ('fries', 'fries'), ('pasta', 'pasta')], validators=[DataRequired()])
second_question = RadioField('How many coins do you have?', choices=[('0','0'), ('1-5','1-5'), ('5-10','5-20'), ('More than 20', 'More than 20')], validators=[DataRequired()])
third_question = RadioField('Do you like chocolate?', choices=[('Yes', 'Yes'), ('No', 'No')], validators=[DataRequired()])
submit = SubmitField('Submit & Continue')
and the form1.html:
<!DOCTYPE html>
<html>
<head>
<title>PREVIOUS EXPERIENCE</title>
</head>
<body>
<main class="container signup-section">
<div class="section-content">
<h2>Please Answer the following questions</h2>
<form method="POST" action="/form1">
{{ form.hidden_tag() }}
<div class="form-group">
{{ form.first_question.label }}
{% if form.first_question.errors %}
{% for error in form.first_question.errors %}
<p class="error-message">{{ error }}</p>
{% endfor %}
{% endif %}
{{ form.first_question }}
</div>
<div class="form-group">
{{ form.second_question.label }}
{% if form.second_question.errors %}
{% for error in form.second_question.errors %}
<p class="error-message">{{ error }}</p>
{% endfor %}
{% endif %}
{{ form.second_question }}
</div>
<div class="form-group">
{{ form.third_question.label }}
{% if form.third_question.errors %}
{% for error in form.third_question.errors %}
<p class="error-message">{{ error }}</p>
{% endfor %}
{% endif %}
{{ form.third_question }}
</div>
{{ form.submit(class="btn-primary") }}
</form>
</div>
<p>ID TEST:</p>
{{ ux_uuid }}
</main>
</body>
</html>
and the welcomepage:
<!DOCTYPE html>
<html>
<head>
<title>WELCOMEPAGE</title>
</head>
<body><p> ENTRY PAGE / START WELCOME </p>
Continue</body>
</html>
Here are all files to test: stack.zip
This question already has answers here:
Determine which WTForms button was pressed in a Flask view
(1 answer)
Validate WTForm form based on clicked button
(1 answer)
Closed 6 years ago.
Imagine I need to build a travel planning form like this:
Going from [_Picadily_Circus____]
Going to [_Marylebone_____]
(Starting by) (Arriving by) [5]:[30][pm]
Both (Starting by) (Arriving by) are submit buttons. The example is artificial to show a two-button usecase so let's not discuss usability.
How do I do this with Flask and WTForms?
How to see which button was actually pressed?
Your form's submit button's data value will be True if it was pressed. See very simple example below of a form with two submit buttons and a single input field.
from flask import Flask, render_template, flash
from flask.ext.wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'well-secret-password'
class MyForm(Form):
name = StringField(label='Name', validators=[DataRequired()])
starting = SubmitField(label='Starting')
ending = SubmitField(label='Ending')
#app.route('/', methods=['GET', 'POST'])
def index():
form = MyForm()
if form.validate_on_submit():
print "Starting data Value : {value}".format(value=form.starting.data)
print "Ending data Value : {value}".format(value=form.ending.data)
flash(
"You submitted name {name} via button {button}".format(
name=form.name.data,
button="Starting" if form.starting.data else "Ending"
)
)
return render_template('index.html', form=form)
if form.errors:
for error_field, error_message in form.errors.iteritems():
flash("Field : {field}; error : {error}".format(field=error_field, error=error_message))
return render_template('index.html', form=form)
if __name__ == '__main__':
app.run(debug=True)
Here's the template file index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class=flashes>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<form method="POST" action="{{ url_for('index') }}">
{{ form.csrf_token }}
{{ form.name.label }} {{ form.name(size=20) }}
<br><br>
{{ form.starting }}
{{ form.ending }}
</form>
</body>
</html>