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>
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 have a flask form that I am using to get input to pass into a class. When I am trying to submit it, it is not showing any error and just reloading the same page. I have made sure that the csrf token is included in the jinja template as well. Here is the code:
Forms.py
class addTripForm(FlaskForm):
location = StringField('Location', validators=[DataRequired()])
startdate = DateField('Start Date', format='%Y=%m-%d', validators=[DataRequired()])
enddate = DateField('End Date', format='%Y=%m-%d', validators=[DataRequired()])
submit = SubmitField('Submit')
app.py
def check_form(form):
print(form.errors)
if form.submit():
print("submitted")
if form.validate():
print("validated")
#app.route('/add', methods=['GET','POST'])
def add():
form = addTripForm(request.form)
check_form(form)
if form.validate_on_submit():
# if form is valid, use fields to create and store a trip
print("adding trip")
user.add_trip(form.location.data,
form.startdate.data,
form.enddate.data)
return redirect('/trips')
return render_template('add.html', form=form)
add.html
{% extends 'base.html' %}
{% block content %}
<body>
<h3> Select a location and date range</h3>
<form action="" method="POST" name="addtrip">
{{ form.csrf_token }}
{{ form.location.label }} {{form.location}}
{{ form.startdate.label }} {{ form.startdate(class='datepicker') }}
{{ form.enddate.label }} {{form.enddate(class='datapicker')}}
<p>
{{ form.submit(class='btn btn-primary') }}
</p>
</form>
</body>
{% endblock %}
You should change your DateFields' format to '%Y-%m-%d' from '%Y=%m-%d' and then your code will work. The date value is formatted as yyyy-mm-dd in the HTTP Request.
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
To make my question clearer, here is a little application that takes a sentence input and outputs that sentence twice.
I have base.html:
<html>
<head>
<title> My site </title>
<body>
{% block content %}{% endblock %}
</body>
</html>
and index.html:
{% extends "base.html" %}
{% block content %}
{{ s }}
<form action="" method="post" name="blah">
{{ form.hidden_tag() }}
{{ form.sentence(size=80) }}
<input type="submit" value="Doubler"></p>
</form>
{% endblock %}
Here is part of views.py:
from forms import DoublerForm
#app.route('/index')
def index():
form = DoublerForm()
if form.validate_on_submit():
s = form.sentence.data
return render_template('index.html', form=form, s=str(s) + str(s))
return render_template('index.html', form=form, s="")
And here is forms.py, without all the imports:
class DoublerForm(Form):
sentence = StringField(u'Text')
This seems to work OK. But what I would like is to have my input form in the base.html template so that this shows up on all pages that extend it, not just the index page. How can I move the form to the base.html and instantiate the form for all views that extend base.html?
You can use the flask.g object and flask.before_request.
from flask import Flask, render_template, g
from flask_wtf import Form
from wtforms import StringField
#app.before_request
def get_default_context():
"""
helper function that returns a default context used by render_template
"""
g.doubler_form = DoublerForm()
g.example_string = "example =D"
#app.route('/', methods=["GET", "POST"])
def index():
form = g.get("doubler_form")
if form.validate_on_submit():
s = form.sentence.data
return render_template('index.html', form=form, s=str(s) + str(s))
return render_template('index.html', form=form, s="")
You can also explicitly define a context function
def get_default_context():
"""
helper function that returns a default context used by render_template
"""
context = {}
context["doubler_form"] = form = DoublerForm()
context["example_string"] = "example =D"
return context
and is used like this
#app.route('/faq/', methods=['GET'])
def faq_page():
"""
returns a static page that answers the most common questions found in limbo
"""
context = controllers.get_default_context()
return render_template('faq.html', **context)
Now, you'll have whatever objects you add to the context dictionary available in all templates that unpack the context dictionary.
index.html
{% extends "base.html" %}
{% block content %}
{{ s }}
{% endblock %}
base.html
<html>
<head>
<title> My site </title>
<body>
{% block content %}{% endblock %}
<form action="" method="post" name="blah">
{{ doubler_form.hidden_tag() }}
{{ doubler_form.sentence(size=80) }}
{{ example_string }}
<input type="submit" value="Doubler"></p>
</form>
</body>
</html>