I am new to Python and Flask. I am trying to use WTForm FlaskForm, and having problems with choices in SelectField. I am building my forms like this.
class FightForm(FlaskForm):
fight_id = StringField('fight_id', render_kw={'readonly': True})
fight_type_id = SelectField('fight_type_id',
choices=[(t.fight_type_id, t.type_name) for t in fight_type.query.all()], validate_choice=False, validators=[DataRequired()])
These choices appear to only load 1 time. If I go add a new fight_type, I have to stop the application and restart if for it to refresh.
Also, I found this answer, flask wtforms selectfield choices not update, but FlaskForm does not trigger the __init__ function this suggests.
I changed it temporarily to Form anyway and got an error saying fight_type_id does not belong to form (paraphrasing).
I would like for these to refresh every time I call the page.
I think you should take a look at the WTForms-SQLAlchemy extension.
The QuerySelectField dynamically loads all database entries based on a submitted database query. A column can be defined for the label of an entry. When the form is submitted, the selected object is automatically queried from the database and is returned by the field.
Flask (app.py)
from flask import (
Flask,
redirect,
render_template,
request,
url_for
)
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from flask_wtf.csrf import CSRFProtect
from wtforms.validators import InputRequired
from wtforms_sqlalchemy.fields import QuerySelectField
app = Flask(__name__)
app.secret_key = b'your secret here'
db = SQLAlchemy(app)
csrf = CSRFProtect(app)
class FightType(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False, unique=True)
class Fight(db.Model):
id = db.Column(db.Integer, primary_key=True)
type_id = db.Column(db.Integer, db.ForeignKey('fight_type.id'))
type = db.relationship('FightType', backref='fights')
class FightForm(FlaskForm):
type = QuerySelectField(
get_label='name',
query_factory=lambda: FightType.query.all(),
validators=[InputRequired()]
)
with app.app_context():
db.drop_all()
db.create_all()
types = [FightType(name=f'type-{i}') for i in range(10)]
db.session.add_all(types)
db.session.commit()
#app.route('/')
def index():
fights = Fight.query.all()
return render_template('index.html', **locals())
#app.route('/fight/new', methods=['GET', 'POST'])
def fight_create():
form = FightForm(request.form)
if form.validate_on_submit():
fight = Fight()
form.populate_obj(fight)
db.session.add(fight)
db.session.commit()
return redirect(url_for('index'))
return render_template('create.html', **locals())
#app.route('/fight/<int:fight_id>/edit', methods=['GET', 'POST'])
def fight_update(fight_id):
fight = Fight.query.get_or_404(fight_id)
form = FightForm(request.form, obj=fight)
if form.validate_on_submit():
form.populate_obj(fight)
db.session.commit()
return redirect(url_for('index'))
return render_template('update.html', **locals())
HTML (templated/index.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Index</title>
</head>
<body>
New
<ul>
{% for f in fights -%}
<li>
<a href="{{ url_for('fight_update', fight_id=f.id) }}">
{{ f.id }} - {{ f.type.name }}
</a>
</li>
{% endfor -%}
</ul>
</body>
</html>
HTML (templates/create.html, templates/update.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Fight</title>
</head>
<body>
<form method="post">
{{ form.csrf_token }}
{{ form.type.label }} {{ form.type() }}
<input type="submit" value="Go">
</form>
</body>
</html>
Related
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>
I want to both change the width and the text-align of an input tag (StringField) of a form (a Flask wtforms wtf.quick_form). From a similar question (Can you adjust the width of a question in flask-wtforms?), I get that to change the width you establish the 'render_kw' argument of the StringField as something like this: {'style': 'width:20rem'}.
However, how would I change the 'text-align' property to 'center' at the same time? I tried to change the StringField of the form class to the following, but it did not result in the desired change:
review_edit = StringField(label="Your Review", validators=[DataRequired()], render_kw={'style':'width:20rem; text-align:center;'})
Here is the code of the html file called 'edit.html', which is inside a "templates" folder:
{% extends 'bootstrap/base.html' %}
{% import "bootstrap/wtf.html" as wtf %}
{% block styles %}
{{ super() }}
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Nunito+Sans:300,400,700">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Poppins:300,400,700">
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
{% endblock %}
{% block title %}Edit Movies{% endblock %}
{% block content %}
<p class="description">Edit Movie Rating</p>
{{wtf.quick_form(form, novalidate=True) }}
{% endblock %}
Here's the "styles.css" file code, which is inside a 'static' folder and inside a nested 'css' folder:
https://www.codeply.com/p/HB2eprf0Aq
Here's the minimum reproducible relevant Python code (main.py) [MAKE SURE YOU ADD APPROPRIATE SECRET KEY IN THE APP.CONFIG['SECRET_KEY'] LINE (line 10)]:
from flask import Flask, render_template, redirect, url_for, request
from flask_bootstrap import Bootstrap
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
import requests
app = Flask(__name__)
app.config['SECRET_KEY'] = '[INSERT_SECRET_KEY_HERE]'
app.config['SQLALCHEMY_DATABASE_URI']='sqlite:///movies1.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db= SQLAlchemy(app)
class Movie(db.Model):
id = db.Column(db.Integer, primary_key=True)
rating = db.Column(db.Float, nullable=False)
review = db.Column(db.String(250), nullable=False)
db.create_all()
new_movie = Movie(rating=8.0, review="A great part")
db.session.add(new_movie)
db.session.commit()
all_movies = db.session.query(Movie).all()
Bootstrap(app)
# EDIT Form
class EditForm(FlaskForm):
rating_edit = StringField(label="Your Rating Out of 10",validators= [DataRequired()])
review_edit = StringField(label="Your Review", validators= [DataRequired()])
submit = SubmitField(label="Submit")
#app.route("/edit",methods=["POST","GET"])
def edit():
edit_form = EditForm()
edit_form.validate_on_submit()
movie_ = db.session.query(Movie).first()
if edit_form.validate_on_submit():
new_rating = float(edit_form.rating_edit.data)
new_review = edit_form.review_edit.data
movie_.rating = new_rating
movie_.review = new_review
db.session.commit()
return redirect(url_for('edit'))
return render_template("edit.html",movie=movie_, form = edit_form)
if __name__ == '__main__':
app.run(debug=True)
Thanks for your help.
I'd like to compose Form fields in code, dynamically, according to the type of data.
I have app.py file:
from flask import Flask, render_template
from flask_wtf import FlaskForm
from wtforms import StringField, IntegerField, FormField, FieldList
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secretomucho'
class StringForm(FlaskForm):
string = StringField()
class IntegerForm(FlaskForm):
integer = IntegerField()
class EntriesForm(FlaskForm):
strings = FieldList(FormField(StringForm))
integers = FieldList(FormField(IntegerForm))
#app.route('/', methods=['GET', 'POST'])
def index():
strs = [{"string": "First String"}, {"string": "Second String"}]
ints = [{"integer": 1}, {"integer": 2}]
form = EntriesForm(strings=strs, integers=ints)
return render_template('index.html', form=form)
and index.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<form method="POST" action="">
{% for field in form %}
<td> {{ field.label }} </td>
<td> {{ field }} </td>
{% endfor %}
</form>
</body>
</html>
Which produces:
But I'd like to have a less grouped look, types of values mixed up, maybe some sections with different data types that can be toggled, etc. All that composed in code according to different data structures.
Something like:
form = MyForm()
form.addIntegerField(value)
form.addStringField(value2)
form.addIntegerField(value3)
return render_template('index.html', form=form)
Is this even possible?
I was able to achieve that with https://wtforms.readthedocs.io/en/2.3.x/specific_problems/#dynamic-form-composition
The key was to use an internal subclass:
from flask import Flask, render_template
from flask_wtf import FlaskForm
from wtforms import StringField, IntegerField,
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secretomucho'
#app.route('/', methods=['GET', 'POST'])
def index():
class MyForm(FlaskForm):
pass
setattr(MyForm, 'test1', StringField('StringField 1'))
setattr(MyForm, 'test2', IntegerField('IntegerField 1'))
setattr(MyForm, 'test3', IntegerField('IntegerField 2'))
setattr(MyForm, 'test4', StringField('StringField 2'))
form = MyForm()
return render_template('index.html', form=form)
Validation on the length of the string in Wtform does not work, I tried to use "if form.validate_on_submit ():" in the post method, but it gives the value:False regardless of the parameters in the input field.
main.py
from flask import Flask, render_template, request, redirect, url_for
from forms import Article
from models import db, Library
app = Flask(__name__)
app.config["SECRET_KEY"] = "hard_string_security"
#app.route('/', methods=["GET", "POST"])
def index():
form = Article()
date_db = Library.select()
# if form.validate_on_submit():
if request.method == "POST":
db.connect()
new_article = Library(titleArticle=form.title, contentArticle=form.content)
new_article.save()
db.close()
print(form.validate_on_submit())
return redirect(url_for(".index"))
return render_template("index.html", date_db=date_db, form=form)
forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, TextAreaField
from wtforms.validators import DataRequired, Length
class Article(FlaskForm):
title = StringField("Input title arcicle", validators=[Length(min=1, max=5),DataRequired()])
content = TextAreaField("Input content arcicle", validators=[DataRequired()])
submit = SubmitField("Submit")
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Index</title>
</head>
<body>
<h2>Create Article</h2>
<form method="post" action="/">
{{ form.title.label }}{{ form.title }}<br>
{{ form.content.label }}{{ form.content }}<br>
{{ form.submit }}<br><br>
</form>
</body>
</html>
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)