I have a form, which has to have some fields multiple times. Thats why I extracted all fields, that should appear multiple times, into the class AdvandcedGame. All fields that are unique are in the Form class. The Form class is also the only one with a submit-button. When I press submit, all instances of AdvancedGame(A,B,C) are unified(A,A,A). Can you tell me why and how could I fix this?
Here is the code as simplified as possible
app.py
from flask import Flask, render_template
from flask_restful import Resource, Api
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
class AdvandcedGame(FlaskForm):
name = StringField('name')
class Ui(FlaskForm):
submit = SubmitField('submit')
app = Flask(__name__)
api = Api(app)
app.config['JSON_SORT_KEYS'] = False
app.config['SECRET_KEY'] = "303"
#app.route('/', methods=['GET', 'POST'])
def root():
form = Ui()
games = [AdvandcedGame(name='A'), AdvandcedGame(name='B'), AdvandcedGame(name='C')]
for game in games:
app.logger.info(game.name.data)
# outputs
# A
# B
# C
if form.validate_on_submit():
for game in games:
app.logger.info(game.name.data)
# outputs
# A
# A
# A
return render_template('index.html', form=form, games=games)
# Run the application
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
templates/index.html
<html>
<head>
</head>
<body>
{% if form is defined %}
<form method="POST" action="{{ form.url }}">
{{ form.csrf_token }}
<table>
<tr>
<td>
{{ games[0].name.label }}
</td>
{% for game in games %}
<td>
{{ game.name }}
</td>
{% endfor %}
</tr>
<tr>
<td colspan=3>
{{ form.submit}}
</td>
</tr>
</table>
</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>
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>
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
I'm learning to use a database in flask at https://www.tutorialspoint.com/flask/flask_sqlalchemy.htm
and my problem is that data doesn't show up on html!
My code :
from flask import Flask, redirect, url_for, request, render_template, session, escape, flash
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///students.sqlite3'
db = SQLAlchemy(app)
class Students(db.Model):
id = db.Column(db.Integer, primary_key=True)
stu_num = db.Column('Student Number', db.Integer)
stu_name = db.Column('Name', db.String(100))
def __init__(self, number, name):
self.num = number
self.name = name
#app.route('/students/')
def show_students():
return render_template('show_students.html', students=Students.query.all())
#app.route('/addstudents/', methods=['POST', 'GET'])
def add_students():
if request.method == 'POST':
if not request.form['stu_num'] or not request.form['stu_name']:
flash('Please fill in all the fields')
else:
student = Students(request.form['stu_num'], request.form['stu_name'])
db.session.add(student)
db.session.commit()
flash('Record added')
return redirect(url_for('show_students'))
return render_template('add_students.html')
if __name__ == '__main__':
db.create_all()
app.run(debug = True)
show_students.html:
<!doctype>
<html>
<body>
{% for message in get_flashed_messages() %}
{{ message }}
{% endfor %}
<h2>Add Students</h2>
<table>
<tr>
<th>Id</th>
<th>Student Number</th>
<th>Name</th>
</tr>
{% for student in students %}
<tr>
<td>{{ student.id }}</td>
<td>{{ student.num }}</td>
<td>{{ student.name }}</td>
</tr>
{% endfor %}
</table>
</body>
</html>
add_students.html:
<!doctype>
<html>
<body>
{% for message in get_flashed_messages() %}
{{ message }}
{% endfor %}
<form action="http://localhost:5000/addstudents/" method="post">
<p>Student Number: <input type="text" name="stu_num"></p>
<p>Student Name: <input type="text" name="stu_name"></p>
<p><input type="submit" value="Submit"></p>
</form>
</body>
</html>
When I add data to the database, Id, which I set to auto-increment shows up, but the rest of data isn't shown.
You are using inconsistent variable names here
in init function, use
def __init__(self, number, name):
student.stu_num=number
student.stu_name=name
in show_students.html try using
<td>{{ student.stu_num }}</td>
<td>{{ student.stu_name }} </td>
SQLAlchemy provides an __init__ function for you which takes care of creating new objects, initializing fields, database, etc. By writing your own __init__, you are overriding SQLAlchemy's
__init__(), and preventing it from doing its thing!
So try getting rid of your __init__ function!
(Or if there's stuff you want to do in __init__, then make sure to call super().__init__ before or after doing whatever it is that you want to do.)
(But to start, just try getting rid of it...)