First of all, I'm not sure if this issue should be addressed in Flask-WTF or Bootstrap-Flask so I opened the issue for both projects. Here is the Flask-WTF side issue: pallets-eco/flask-wtf#695
Not so long ago, this commit has been added to Flask-WTF: pallets-eco/flask-wtf@adf674f
What it does is basically if a class is present at widget rendering, then it ignores the RECAPTCHA_DIV_CLASS config (or even the default value). This config used to be the way to define the class which the h-captcha and re-captcha scripts are looking for when they inject their iframes.
Bootstrap-Flask seem to treat captcha fields as regular fields, this it adds the form-control class to it. (see.:
|
{%- else -%} |
|
{% if field.errors %} |
|
{{ field(class="form-control is-invalid%s" % extra_classes, **kwargs)|safe }} |
|
{% else %} |
|
{{ field(class="form-control%s" % extra_classes, **kwargs)|safe }} |
|
{% endif %} |
|
{%- endif %} |
). This causes the class required by captcha above not to be added.
Since the class is not present, h-captcha does not work.
Now one way to "fix" this is to add class="h-captcha" to all templates, but that's not always possible, especially when templates are coming from a third party library. Also I don't think that's very elegant either, having to put functionality related code to templates.
How Bootstrap-Flask could fix the situation: Add special handling to fields with the type of RecaptchaField (as it is already done for other types like SelectField, DecimalRangeField, etc.) and does not pass any class= to it. This would allow the re-captcha field to use the configured value. And also not having form-control on the captcha field, removes the ugly border around it. (That wasn't present when the config would override all classes before)
Reproduce
repro.py:
from flask import Flask, render_template
from flask_bootstrap import Bootstrap5
from flask_wtf import RecaptchaField, FlaskForm
class CaptchaForm(FlaskForm):
captcha = RecaptchaField()
app = Flask(__name__)
app.config['SECRET_KEY'] = 'hunter2'
app.config['RECAPTCHA_PUBLIC_KEY'] = '00000000-0000-0000-0000-000000000000'
app.config['RECAPTCHA_PRIVATE_KEY'] = 'ES_000000000000000000000000000000000'
app.config['RECAPTCHA_DIV_CLASS'] = 'h-captcha'
app.config['RECAPTCHA_SCRIPT'] = "https://js.hcaptcha.com/1/api.js"
app.config['RECAPTCHA_VERIFY_SERVER'] = "https://api.hcaptcha.com/siteverify"
Bootstrap5(app)
@app.route("/")
def captcha():
form = CaptchaForm()
return render_template("repro.html.j2", form=form)
app.run(debug=True)
templates/repro.html.j2
{% from 'bootstrap5/form.html' import render_form %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
{{ bootstrap.load_css() }}
</head>
<body>
{{ render_form(form) }}
{{ bootstrap.load_js() }}
</body>
</html>
Result:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/css/bootstrap.min.css" integrity="sha384-SgOJa3DmI69IUzQ2PVdRZhwQ+dy64/BUtbMJw1MZ8t5HZApcHrRKUc4W0kG879m7" crossorigin="anonymous">
</head>
<body>
<form action="" method="post"
class="form" role="form">
<input id="csrf_token" name="csrf_token" type="hidden" value="IjFkYzZlY2RiMDdlOTU3OGFkMjliNGM1NWRhNzE3ZWZmMzkzZDY4MDUi.aoCPjA.3iwUH6fnmrg79yshyg3cw0oTE9Q">
<div class="mb-3"><label class="form-label" for="captcha"></label>
<script src='https://js.hcaptcha.com/1/api.js' async defer></script>
<div class="form-control" data-sitekey="c832d69a-2e52-482c-9566-169ee5d6da56" id="captcha"></div>
</div>
</form>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/js/bootstrap.min.js" integrity="sha384-VQqxDN0EQCkWoxt/0vsQvZswzTHUVOImccYmSyhJTp7kGtPed0Qcx8rK9h9YEgx+" crossorigin="anonymous"></script>
</body>
</html>
As you can see, the class="h-captcha" is not added to the div.
Environment:
- Python version: 3.14
- Flask-WTF version: 1.3.0
- Flask version: 3.1.3
- Bootstrap-Flask version: 2.5.0
First of all, I'm not sure if this issue should be addressed in Flask-WTF or Bootstrap-Flask so I opened the issue for both projects. Here is the Flask-WTF side issue: pallets-eco/flask-wtf#695
Not so long ago, this commit has been added to Flask-WTF: pallets-eco/flask-wtf@adf674f
What it does is basically if a
classis present at widget rendering, then it ignores theRECAPTCHA_DIV_CLASSconfig (or even the default value). This config used to be the way to define the class which the h-captcha and re-captcha scripts are looking for when they inject their iframes.Bootstrap-Flask seem to treat captcha fields as regular fields, this it adds the
form-controlclass to it. (see.:bootstrap-flask/flask_bootstrap/templates/bootstrap5/form.html
Lines 236 to 242 in 15d29fd
Since the class is not present, h-captcha does not work.
Now one way to "fix" this is to add
class="h-captcha"to all templates, but that's not always possible, especially when templates are coming from a third party library. Also I don't think that's very elegant either, having to put functionality related code to templates.How Bootstrap-Flask could fix the situation: Add special handling to fields with the type of
RecaptchaField(as it is already done for other types likeSelectField,DecimalRangeField, etc.) and does not pass anyclass=to it. This would allow the re-captcha field to use the configured value. And also not having form-control on the captcha field, removes the ugly border around it. (That wasn't present when the config would override all classes before)Reproduce
repro.py:
templates/repro.html.j2
Result:
As you can see, the
class="h-captcha"is not added to the div.Environment: