Good day
I'm struggling to find a way to check in jinja2 if a wtform check box is checked.
I want to be able to make an if statement that shows when a check box is check that it will display additional input fields like:
{% if form.BooleanField == checked %}
display fields
{% endif %}
To enable one or more form fields you can use the disabled attribute of an input field or field set. Using a style sheet rule, the respective element is displayed or not.
When the user clicks the checkbox, an event listener updates the disabled attribute based on the state of the box.
An additional validator is required to validate the optional fields on the server side as well. This expects an entry in the respective field as long as the value of the referenced form field has been set.
Below is the complete code for a sample application.
from flask import (
Flask,
render_template,
request
)
from flask_wtf import FlaskForm
from wtforms import (
BooleanField,
StringField,
SubmitField
)
from wtforms.validators import (
DataRequired,
Optional
)
app = Flask(__name__)
app.secret_key = 'your secret here'
class RequiredIf(DataRequired):
field_flags = ('requiredif',)
def __init__(self, other_field_name, message=None, *args, **kwargs):
self.other_field_name = other_field_name
self.message = message
def __call__(self, form, field):
other_field = form[self.other_field_name]
if other_field is None:
raise Exception('no field named "%s" in form' % self.other_field_name)
if bool(other_field.data):
super(RequiredIf, self).__call__(form, field)
class ExampleForm(FlaskForm):
enabled = BooleanField(validators=[Optional()])
field1 = StringField(validators=[RequiredIf('enabled')])
field2 = StringField(validators=[RequiredIf('enabled')])
submit = SubmitField()
#app.route('/', methods=['GET', 'POST'])
def index():
form = ExampleForm(request.form)
if form.validate_on_submit():
for f in form:
print(f'{f.name}: {f.data}')
return render_template('index.html', **locals())
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Index</title>
<style>
fieldset[disabled], input[disabled] {
display: none;
}
</style>
</head>
<body>
<form method="post">
{{ form.csrf_token }}
<div>
{{ form.enabled }}
{{ form.enabled.label() }}
</div>
<fieldset name="optional-fields" {% if not form.enabled.data %}disabled{% endif %}>
<div>
{{ form.field1.label() }}
{{ form.field1 }}
</div>
<div>
{{ form.field2.label() }}
{{ form.field2 }}
</div>
</fieldset>
{{ form.submit }}
</form>
<script type="text/javascript">
(function() {
const enabledField = document.getElementById('enabled');
const optionalFieldset = document.querySelector('fieldset[name="optional-fields"]');
enabledField.addEventListener('change', function(event) {
optionalFieldset.disabled = !this.checked;
});
})();
</script>
</body>
</html>
Related
I am using generic MethodViews in my Flask application together with Flask-MongoEngine. I am also trying to automate form creation based on the given model by using the model_form(db_model) function. Lastly, I am trying to let Bootstrap-Flask take care of rendering the produced form. The problem is that there is no submit button, because the form returned by model_form does not include a SubmitField.
Is there a way to append a submit field before the form is rendered?
Here is a minimally viable app
from datetime import datetime
from flask import Flask, Blueprint, render_template, url_for
from flask_mongoengine import MongoEngine
from flask_bootstrap import Bootstrap5
db = MongoEngine()
bootstrap = Bootstrap5()
class Role(db.Document):
meta = { 'collection': 'roles' }
name = db.StringField(verbose_name='Role Name', max_length=24, required=True, unique=True)
description = db.StringField(verbose_name='Role Description', max_length=64, default=None)
date_created = db.DateTimeField(default=datetime.utcnow())
date_modified = db.DateTimeField(default=datetime.utcnow())
def get_id(self):
return str(self.id)
def safe_delete(self):
if self.name == 'admin':
return False, 'Admin role is not removable.'
result, resp = self.delete()
if not result:
return result, resp
return True, f'Privilege {self.name} removed successfully'
class AuthItemView(MethodView):
def __init__(self, model, template):
self.model = model
self.template = template
def _get_item(self, id):
return self.model.objects.get_or_404(id=id)
def get(self, id):
item = self._get_item(id)
return render_template(self.template, item=item)
class AuthItemEditor(AuthItemView):
def _get_form(self):
form = model_form(self.model, exclude=['date_created', 'date_modified'])
return form()
def get(self, id):
item = self._get_item(id)
form = self._get_form()
return render_template(self.template, item=item, form=form)
def post(self, id):
return 'Yay! Role changes were posted!', 200
staff_bp = Blueprint('staff', __name__)
staff_bp,add_url_rule('/roles/<id>', view_func=AuthItemView.as_view('role_info', Role, 'roleinfo.html')
staff_bp.add_url_rule('/roles/<id>/edit', view_func=AuthItemEditor.as_view('role_edit', Role, 'roleedit.html')
def create_app():
app = Flask(__name__)
app.config['MONGODB_SETTINGS'] = {
'db': 'someapp',
'host': 'localhost',
'port': 27017
}
app.config['SECRET_KEY'] = 'some-very-secret-key'
app.register_blueprint(staff_bp)
db.init_app(app)
bootstrap.init_app(app)
return app
if __name__ == __main__:
a = create_app()
a.run()
The roleedit template is where the magic has todoesn't happen:
{% extends 'base.html' %}
{% from 'bootstrap5/form.html' import render_form %}
{% block content %}
<h1>Edit Properties of <em>{{ item.name }}</em></h1>
<p>
{{ render_form(form) }}
<!-- There is no submit button!!! How do I submit??? //-->
</p>
{% endblock %}
Here is a very basic base template:
<!doctype html>
<html lang="en">
<head>
{% block head %}
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% block styles %}
<!-- Bootstrap CSS -->
{{ bootstrap.load_css() }}
{% endblock %}
<title>Page title</title>
{% endblock %}
</head>
<body>
<!-- Your page content -->
{% block content %}{% endblock %}
{% block scripts %}
<!-- Optional JavaScript -->
{{ bootstrap.load_js() }}
{% endblock %}
</body>
</html>
Lastly, the roleinfo template (it's there just to be able to run the app):
{% extends 'base.html' %}
{% block content %}
<h1>Properties of <em>{{ item.name }}</em></h1>
<!-- display item properties as a table or as responsive bootstrap grid //-->
{% endblock %}
You can pass a Form class to model_form. For example:
class BaseForm(Form):
submit = SubmitField('Submit')
class AuthItemEditor(AuthItemView):
def _get_form(self):
form = model_form(self.model, base_class=BaseForm, exclude=['date_created', 'date_modified'])
return form()
# ...
Turns out, it is rather easy...
# ... other imports ...
from wtforms import SubmitField
# ... inside the get_form function
form = model_form(self.model)
setattr(form, 'submit', SubmitField('Submit'))
# now return the fixed form
If anyone has a different approach, please share!
I am new to Flask.
The validate_on_submit() is not working and I also don't know what app.app_context() and app.test_request_context() do in my code.
The only thing I am trying to do is get my form to validate I can't figure out why it's not working.
This is my main.py
from flask import Flask, render_template, request, flash
from the_first_wt_form import MyForm
app = Flask(__name__)
app.config['SECRET_KEY'] = '934kegn298u54kjgnjdkrbg9u939'
with app.app_context():
with app.test_request_context():
a_form = MyForm()
#app.route('/', methods=['GET', 'POST'])
def home():
if request.method == "POST":
name = request.form['name']
print(name)
email = request.form['email']
print(email)
passwrd = request.form['password']
print(passwrd)
con = request.form['confirm']
print(con)
if a_form.validate_on_submit():
print("Good job")
name = request.name.data
print(name)
else:
print('We messed up')
if a_form.errors != {}:
for err in a_form.errors.values():
print(f"There was an error with creating user: {err}")
flash(f"There was an error with creating user: {err}", category='danger')
return render_template('mynewhome.html', form=a_form)
if __name__ == "__main__":
app.run(debug=True)
This is the code from My wt_form.py
from wtforms import StringField, PasswordField, validators, SubmitField
from flask_wtf import FlaskForm
class MyForm(FlaskForm):
name = StringField('name', [validators.Length(min=4, max=25), validators.DataRequired()])
email = StringField('Email Address', [validators.Length(min=6, max=35), validators.Email()])
password = PasswordField('New Password', [
validators.DataRequired(),
validators.EqualTo('confirm', message='Passwords must match')
])
confirm = PasswordField('Repeat Password')
submit = SubmitField('Register')
And Finally this is mynewhome.html
<!DOCTYPE html> <html lang="en"> <head>
<meta charset="UTF-8">
<title>How are you?</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.1.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-uWxY/CJNBR+1zjPWmfnSnVxwRheevXITnMqoEIeG1LJrdI0GlVs/9cVSyPYXdcSF" crossorigin="anonymous">
</head> <body>
<h1> Hello BRo </h1> <br><br> {% with messages = get_flashed_messages(with_categories = true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert--{{ category }}">
<button type="button" class="m1-2 mb-1 close" data-dismiss="alert" aria-label="Close">
{{ message }}
<span aria-hidden="true">×</span>
</button>
</div>
{% endfor %}
{% endif %}
{% endwith %} <br><br>
<div class="container">
<form method="POST" action="/" class="form-register">
{{ form.hidden_tag() }}
{{ form.name.label }} {{ form.name(class = "form-control", Placeholder = "Usern Name") }}
{{ form.email.label }} {{ form.email(class = "form-control", Placeholder = "Email Address") }}
{{ form.password.label }} {{ form.password(class = "form-control", Placeholder = "Password") }}
{{ form.confirm.label }} {{ form.confirm(class = "form-control", Placeholder = "Confirm Password") }}
<br>
{{ form.submit(class = "btn btn-lg btn-block btn-primary") }}
</form> </div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.1.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-kQtW33rZJAHjgefvhyyzcGF3C5TFyBQBA13V1RKPf4uH+bwyzQxZ6CmMZHmNBEfJ" crossorigin="anonymous"></script> </body> </html>
As a new flask user and simple flask usage, you should not need app_context and test_request_context. You can have a look on the documentation to understand them, but you don't need them in this situation.
You have to instantiate your form in the view function home
It is also a better practice to use form data only after validation, as you never know what the user enter in your form.
In the import you were importing the_first_wt_form but you said your file is called wt_form so I made the appropriate changes. But accordingly to your modules setup it might be wrong.
The main.py should look like (I tested it):
from flask import Flask, render_template, request, flash
from wt_form import MyForm
app = Flask(__name__)
app.config['SECRET_KEY'] = '934kegn298u54kjgnjdkrbg9u939'
#app.route('/', methods=['GET', 'POST'])
def home():
a_form = MyForm()
if request.method == "POST":
if a_form.validate_on_submit():
print("Good job")
name = a_form.name.data
print(name)
# (...)
else:
print('We messed up')
if a_form.errors != {}:
for err in a_form.errors.values():
print(f"There was an error with creating user: {err}")
flash(f"There was an error with creating user: {err}", category='danger')
return render_template('mynewhome.html', form=a_form)
if __name__ == "__main__":
app.run(debug=True)
Note that you can access data from the a_form instance directly rather than on the request instance.
Need advice regarding WTForms Flask: I need to use ip address validator and maximum length validator (IPAddress, Length) - they works, but do not give user any error messages, on the other hand InputRequired validator works fine. I checked documentation and have no idea what could be the problem with the code.
// app.py file:
from flask import Flask, render_template
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import InputRequired, IPAddress, Length
app = Flask(__name__)
app.secret_key = "test"
StringField()
class MyForm(FlaskForm):
inp_required_str = StringField("Input required: ", validators=
[InputRequired()])
max_len_str = StringField("Max length < 5: ", validators=
[InputRequired(), Length(max=5, message="Less than 5!")])
ip_address_str = StringField("Is ip address: ", validators=
[InputRequired(), IPAddress(message="Should be ip!")])
button = SubmitField("Click me!")
#app.route('/', methods=["GET", "POST"])
def hello_world():
form = MyForm()
if form.validate_on_submit():
# do some work here
return render_template("test.html", form=form, message="Fine?")
return render_template("test.html", form=form)
if __name__ == '__main__':
app.run()
// html template:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test</title>
</head>
<body>
<form method="post">
{{ form.hidden_tag() }}
{{form.inp_required_str.label}} {{form.inp_required_str}} <br>
<br>
{{form.max_len_str.label}} {{form.max_len_str}} <br> <br>
{{form.ip_address_str.label}} {{form.ip_address_str}} <br> <br>
{{form.button}} <br> <br>
</form>
<h1>{{ message }}</h1>
</body>
</html>
You are not rendering the error messages that get returned by calling validate_on_submit. You need to add some kind of logic to do that for you. InputRequired validator works fine because Wtforms adds an required attribute to your input field and that is managed by the browser itself.
I would suggest you use a macro for that as stated here:
{% 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 %}
I'm trying to raise an error in Jinja2, in a WTForm, the error should be raised if url input is not validated, but when i submit an invalide url, i get a popup saying "Please enter a url".
how do i pass the default popup and add a custom error message ?
here is the main py:
from datetime import datetime
from flask import Flask, render_template, url_for, request, redirect,flash
from logging import DEBUG
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField
from flask.ext.wtf.html5 import URLField
from wtforms.validators import DataRequired , url
app = Flask(__name__)
app.logger.setLevel(DEBUG)
app.config['SECRET_KEY']='{#\x8d\x90\xbf\x89n\x06%`I\xfa(d\xc2\x0e\xfa\xb7>\x81?\x86\x7f\x1e'
#app.route('/')
#app.route('/index')
def index():
return render_template('base.html')
#app.route('/add', methods=['GET','POST'])
def add():
return render_template('add.html')
# HERE IS THE LOGIN FORM
class Login(FlaskForm):
username = StringField('username')
password = PasswordField('password')
url = URLField('url', validators=[DataRequired(),url()])
#app.route('/form', methods=['GET','POST'])
def form():
form = Login()
if form.validate_on_submit():
url = form.url.data
return redirect(url_for('index'))
return render_template('form.html',form = form )
if __name__ =='__main__':
app.run(debug=True)
and here is the template:
<!DOCTYPE html>
<html>
<head>
<title>form</title>
</head>
<body>
<h1>Hello !</h1>
<form method="POST" action="{{url_for('form')}}">
{{ form.hidden_tag() }}
{{ form.csrf_token }}
{{ form.username.label }}
{{ form.username }}
{{ form.password.label }}
{{ form.password }}
{{ form.url.label }}
{{ form.url }}
{% if form.url.errors %} <p> {{error}}</p> {% endif %}
<button type="submit">Submit</button>
</form>
</body>
</html>
Because you're using the data type URLField, this is rendered as a HTML5 "url" form field type.
Your browser recognises this and performs its own validation on the data submitted:
There is no way for you to override this - it's built in to the browser.
If you need to show a custom error message, you might be able to use a TextField instead, and provide your own URL validation logic.
Add your own message instead of default message in your form defination.
url = URLField('url', validators=[DataRequired(),url(message="Please enter a valid url (e.g.-http://example.com/)")])
As Matt Healy before mentiones, it is the browser that validates URLField.
So if you want a custom error message use StringField (TextField is outdated). If required, a custom message can be used as shown below message='text to display'.
Example:
class XYZForm(FlaskForm):
url = StringField('url', validators=[DataRequired(),url(message='Please enter valid URL')])
description = StringField('description')
Of course the *.html should include code to output an error to the page:
<ul>
{% for error in form.url.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
It seems like novalidate attribute works for your case.
I'm trying to implement Select2 field in one of my flask views. Basically I want the same select2 field in my flask application view (not a flask admin modelview) as in Flask-admin model create views. Currently my solution has been QuerySelectField from wtforms that looks something like this
class TestForm(Form):
name= QuerySelectField(query_factory=lambda: models.User.query.all())
This allows me to load and select all the data I need, but it does not provide select2 search box etc. Currently all that I have found is Select2Field and Select2Widget from flask/admin/form/fields and flask/admin/form/widgets similarly like in this post https://stackoverflow.com/questions/24644960/how-to-steal-flask-admin-tag-form-field and also select2 documentation at http://ivaynberg.github.io/select2/
As I understand these could be reusable, meaning there is no need for other custom widgets, custom fields.
Would be grateful if someone could provide more information about select2 field implementation in flask application (including views, templates, forms files and how to correctly "connect" with neccessary js and css files, also how to load the field up with the database model I need).
This worked for me:
...
from wtforms.ext.sqlalchemy.fields import QuerySelectField
from flask_admin.form.widgets import Select2Widget
...
class TestForm(Form):
name= QuerySelectField(query_factory=lambda: models.User.query.all(),
widget=Select2Widget())
And in your template:
{% extends "admin/master.html" %}
{% import 'admin/lib.html' as lib with context %}
{% block head %}
{{ super() }}
{{ lib.form_css() }}
{% endblock %}
{% block body %}
...
{% endblock %}
{% block tail %}
{{ super() }}
{{ lib.form_js() }}
{% endblock %}
I can try to put together a minimal working example if necessary.
I had a similar requirement and have put together a minimal example.
Note the following:
Class TestView defines three routes; a get view, a post view and an Ajax lookup view.
Function get_loader_by_name takes a string name and returns a QueryAjaxModelLoader. This function is used in both the Ajax lookup call and in the TestForm field definitions.
The text displayed in the Select2 widgets is the value returned by the User model's __unicode__ method.
I've used Faker to generate the sample user data.
File app.py:
from flask import Flask, render_template, url_for, request, abort, json, Response
from flask.ext.admin import Admin, BaseView, expose, babel
from flask.ext.admin.contrib.sqla.ajax import QueryAjaxModelLoader
from flask.ext.admin.model.fields import AjaxSelectField, AjaxSelectMultipleField
from flask.ext.sqlalchemy import SQLAlchemy
from wtforms import Form
from faker import Factory
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'super-secret'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
try:
from flask_debugtoolbar import DebugToolbarExtension
DebugToolbarExtension(app)
except:
pass
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.Unicode(length=255), nullable=False)
last_name = db.Column(db.Unicode(length=255), nullable=False)
email = db.Column(db.Unicode(length=254), nullable=False, unique=True)
def __unicode__(self):
return u"{first} {last}; {email}".format(first=self.first_name, last=self.last_name, email=self.email)
def get_loader_by_name(name):
_dicts = {
'user': QueryAjaxModelLoader(
'user',
db.session, User,
fields=['first_name', 'last_name', 'email'],
page_size=10,
placeholder="Select a user"
)
}
return _dicts.get(name, None)
class TestView(BaseView):
def __init__(self, name=None, category=None,
endpoint=None, url=None,
template='admin/index.html',
menu_class_name=None,
menu_icon_type=None,
menu_icon_value=None):
super(TestView, self).__init__(name or babel.lazy_gettext('Home'),
category,
endpoint or 'admin',
url or '/admin',
'static',
menu_class_name=menu_class_name,
menu_icon_type=menu_icon_type,
menu_icon_value=menu_icon_value)
self._template = template
#expose('/', methods=('GET',))
def index_view(self):
_form = TestForm()
return self.render(self._template, form=_form)
#expose('/', methods=('POST',))
def post_view(self):
pass
#expose('/ajax/lookup/')
def ajax_lookup(self):
name = request.args.get('name')
query = request.args.get('query')
offset = request.args.get('offset', type=int)
limit = request.args.get('limit', 10, type=int)
loader = get_loader_by_name(name)
if not loader:
abort(404)
data = [loader.format(m) for m in loader.get_list(query, offset, limit)]
return Response(json.dumps(data), mimetype='application/json')
# Create admin and Test View
admin = Admin(app, name='Admin', template_mode='bootstrap3')
admin.add_view(TestView(template='test.html', name="Test", url='/test', endpoint='test'))
class TestForm(Form):
single_user = AjaxSelectField(
loader=get_loader_by_name('user')
)
multiple_users = AjaxSelectMultipleField(
loader=get_loader_by_name('user')
)
#app.route('/')
def index():
return render_template("index.html", link=url_for('test.index_view'))
def build_db():
db.drop_all()
db.create_all()
fake = Factory.create()
for index in range(0, 1000):
_first_name = fake.first_name()
_last_name = fake.last_name()
_user_db = User(
first_name=_first_name,
last_name=_last_name,
email="{first}.{last}{index}#example.com".format(first=_first_name.lower(), last=_last_name.lower(), index=index)
)
db.session.add(_user_db)
db.session.commit()
#app.before_first_request
def before_first_request():
build_db()
if __name__ == '__main__':
app.debug = True
app.run(port=5000, debug=True)
File templates/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test Select2</title>
</head>
<body>
Go to the test form
</body>
</html>
File templates/test.html:
{% import 'admin/static.html' as admin_static with context %}
{% import 'admin/lib.html' as lib with context %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test Select2</title>
<link href="{{ admin_static.url(filename='bootstrap/bootstrap3/css/bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='bootstrap/bootstrap3/css/bootstrap-theme.min.css') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='admin/css/bootstrap3/admin.css') }}" rel="stylesheet">
{{ lib.form_css() }}
</head>
<body>
<div class="container">
<div class="row">
<div class="col-sm-10 col-sm-offset-2">
<form>
{{ lib.render_form_fields(form) }}
</form>
</div>
</div>
</div>
<script src="{{ admin_static.url(filename='vendor/jquery-2.1.1.min.js') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='bootstrap/bootstrap3/js/bootstrap.min.js') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='vendor/moment-2.8.4.min.js') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='vendor/select2/select2.min.js') }}" type="text/javascript"></script>
{{ lib.form_js() }}
</body>
</html>
Update July 2018
Added a standalone Flask extension available on Github - Flask-Select2 - WIP.
I recently implemented a "tags" field in the front-end of a Flask app, using Select2 and WTForms. I wrote a sample app, demonstrating how I got it working (the view code for populating the select options, and for dynamically saving new options, is where most of the work happens):
https://github.com/Jaza/flasktaggingtest
You can see a demo of this app at:
https://flasktaggingtest.herokuapp.com/
No AJAX autocomplete in my sample (it just populates the select field with all available tags, when initializing the form). Other than that, should be everything that you'd generally want for a tagging widget in your Flask views / templates.
Other answers seem not to work anymore. What I did to make it work is:
On the backend:
from wtforms.ext.sqlalchemy.fields import QuerySelectField, QuerySelectMultipleField
...
users = users = QuerySelectMultipleField('users', query_factory=lambda: User.query.order_by(User.fullname).all(),
get_label=lambda u: u.fullname, get_pk=lambda u: u.id)
Then in the frontend:
$("#users").attr("multiple", "multiple").select2();
You need to manually add css and js for select2.