Message not flashing on some WTForm validation methods - python

This question is related to this question, but focussing on one of the underlying issues I didn't know about when asking that other question.
I'm using Python, Flask and WTForms to make a form. Here's the python:
from flask import render_template
from flask_wtf import FlaskForm
from wtforms import DecimalField, SubmitField
from wtforms.validators import NumberRange, DataRequired
class NumberForm(FlaskForm):
question_one = DecimalField('Question 1', validators=[NumberRange(min=0, max=10)])
question_two = DecimalField('Question 2', validators=[DataRequired()])
submit = SubmitField('Submit')
#app.route('some_route/', methods=['GET', 'POST'])
def page():
form = NumberForm()
if form.validate_on_submit():
return some_success_or_other
return render_template('page.html', form=form)
And the HTML:
<form method="POST">
<div class="form-group-row">
{{ form.hidden_tag() }}
{{ form.question_one.label }}
<div>
{{ form.question_one }}
</div>
</div>
<div class="form-group-row">
{{ form.question_two.label }}
<div>
{{ form.question_two }}
</div>
</div>
<div class="form-group-row">
{{ form.submit }}
</div>
</form>
The two validators I'm using (NumberRange and DataRequired) behave differently. When data isn't entered in field 2 and the submit button is clicked, the form isn't submitted and an error message pops up next to the field saying "Please fill in this field".
When strings (rather than decimals), or numbers outside the range is submitted in field 1, the form does submit when the button is pressed.
Although the error is logged (and can be shown using {{ form.errors }}, I wanted the same behaviour for both validators - I'd like the form to be prevented from submitting when invalid numbers/strings are entered in field 1.
Anyone know why the validators behave differently?

HTML5 introduces the required attribute which makes it mandatory to fill a field and block form validation if one of the fields (affected by this attribute) has not been populated; this attribute is only available for the input tag.
With flask-wtf, when you use a field with the required validator, it will automatically generate a html field with the required attribute, which will block the form from being sent and display an infobule.
In the case of other validators such as NumberRange, it is not the same.
With pure HTML:
to have the same behavior, ie to check that the number entered is in a well-defined range and to block the sending of the form if it is not the case, it is necessary to use the pattern attribute (more details here). There is no predefined pattern for this particular case, so you will need to create a custom pattern using regular expressions. It will give this: <input type="text" pattern="[0-9]">.
Now we just have to reattake the same logic with wtforms...
With wtforms:
<form method="POST">
<div class="form-group-row">
{{ form.hidden_tag() }}
{{ form.question_one.label }}
<div>
{{ form.question_one(pattern="[0-9]") }}
</div>
</div>
<div class="form-group-row">
{{ form.question_two.label }}
<div>
{{ form.question_two }}
</div>
</div>
<div class="form-group-row">
{{ form.submit }}
</div>
</form>
At line {{ form.question_one(pattern="[0-9]") }}, I use the pattern attribute with a regular expression that checks that the number entered is in the range 0 to 9. For larger ranges, you will need to write regular expressions a little more complex (this link is interresting)

Related

Single FlaskForm rendered multiple times on one page using FOR loop

I'm making an application where I need to render identical forms multiple times on one page (number of forms might be from 1 to 20). In the video below I'm using simple for loop over a range from 0 to 5, just for a test. When I input data into the first form and submit it, all data duplicates to other forms but I need the user to submit data of each form independently from others. In other words, the user can fill in data in some forms (in 1, in 2 or in all forms) and submit those data separately.
Youtube video showing my problem
I have tried to change submit buttons manually with unique ids and names for each form but this didn't help.
I also tried to use Field Enclosures from WTForms but this also didn't help.
Here is my FlaskForm I haven't included all fields to save space
class EnvelopeForm(FlaskForm):
vme_name = StringField('VME')
outside_diameter = DecimalField('OD', validators=[DataRequired()])
submit = SubmitField('build envelope')
Here is my view handler
#app.route('/bokeh', methods=['GET', 'POST'])
def nonuniform_ellipse():
form = EnvelopeForm()
if form.validate_on_submit():
envelope = Envelopes(vme_name=form.vme_name.data,
outside_diameter=form.outside_diameter.data)
db.session.add(envelope)
db.session.commit()
return render_template('vme.html', form=form)
Here is HTML I haven't included all fields to save space.
{% for i in range(0, 5) %}
<form method="POST" action="" name="{{ i }}">
{{ form.hidden_tag() }}
<div>
{{ form.vne_name.label }}
{{ form.vme_name }}
</div>
<div>
{{ form.outside_diameter.label }}
{{ form.outside_diameter }}
</div>
<div>
{{ form.submit) }}
</div>
</form>
{% endfor %}
Here is a solution as per #ser-zhm comment. Now I can render whatever forms I have in DB separately on each other as it is shown on this image.
But when I submit data from one of the forms it is always updates the last record in DB and not the particular row. Lets say I have three empty forms rendered on the page and three records (rows) in my DB with NULL data in each column name referring to each form fields like it is shown here. When I fill in form #2, all the data is saved in row #3 instead of the row #2. So whatever form I fill in it is always the last record is updated.
How can I updated the particular row which is referring to the form ID?
View handler
#app.route('/envelope_forms_test', methods=['GET', 'POST'])
def nonuniform_ellipse():
form_ids = list(map(str, [id[0] for id in
Envelopes.query.with_entities(Envelopes.id).all()]))
forms = []
for form_id in form_ids:
form = EnvelopeForm(prefix=form_id)
forms.append(form)
for form in forms:
if form.submit.data and form.validate_on_submit():
Envelopes.query.filter_by(id=form_id).\
update({'vme_name': form.vme_name.data,
'outside_diameter': form.outside_diameter.data})
db.session.commit()
Here is HTML
{% for form in forms %}
<form method="POST" action="">
{{ form.csrf_token() }}
<div>
{{ form.vne_name.label }}
{{ form.vme_name }}
</div>
<div>
{{ form.outside_diameter.label }}
{{ form.outside_diameter }}
</div>
<div>
{{ form.submit) }}
</div>
</form>
{% endfor %}

Populate textarea field using WTForms

As I understand it, if you want to populate a textarea you place the text between the textarea tags. However I am using WTForms. How can I pre-populate the form from views or in my template?
FORM
class ModuleSectionForm(FlaskForm):
title = StringField('Section Title', validators=[DataRequired()])
description = TextAreaField('Description', validators=[DataRequired()])
submit = SubmitField('Add Section')
VIEW
#modules.route('/update_section/<name>/<title>', methods=['GET', 'POST'])
def update_section(name, title):
form = ModuleSectionForm()
module = Module.objects(title=name).first()
section = None
for sect in module.sections:
if sect.title == title:
section = sect
#if form.validate_on_submit():
#save data
return render_template('modules/update_section.html', section=section, form=form)
TEMPLATE
<form method="post" action="{{ url_for('modules.update_section', name=name) }}">
{{ form.hidden_tag() }}
<div class="form-group">
{{ form.title.label(class="form-control-label") }}
{{ form.title(class="form-control", value=section.title) }}
</div>
<div class="form-group">
{{ form.description.label(class="form-control-label") }}
{{ form.description(class="form-control", default=section.description) }}
</div>
<div class="form-group">
{{ form.submit(class="btn btn-secondary shadow") }}
</div>
</form>
This can be done by just assigning the text to display beforehand (in the view, for instance).
Try edit your view (update_section) this way:
+ form.description.data = 'text you want to display'
And your template as follows:
- {{ form.description(class="form-control", default=section.description) }}
+ {{ form.description(class="form-control") }}
Mind you, there's an alternative way, which is to specify the placeholder attribute (but I guess is not what you want to do here).
Typically with WTForms (let's assume you're using Flask and Bootstrap here), you'll use the value attribute of the input to pre-populate a form field. Note that pre-populating a field is distinct from providing a 'placeholder', which is just an ephemeral hint. So usually we pre-populate like this:
<div class="form-group row">
<label for="form_subject" class="col-sm-2 col-form-label">Subject</label>
<div class="col-sm-7">
<input class="form-control" id="form_framework" name="form_framework"
value="{{ instruct.Subject }}">
</div>
</div>
With Flask and Bootstrap, the name attribute is required to pass the value back to the Controller upon submission, the value attribute is used to pre-populate the field from the object - in this case, our controller has passed in an object called instruct, which has an attribute Subject.
But you have to be aware that different kinds of inputs in WTForms have different attributes, and this is left as a fun challenge for the developer to figure out.
TextArea doesn't have a value attribute, so in order to pre-populate the field, you have to provide the value between the tags, like so (again, using Bootstrap here in case any of these tags are unfamiliar):
<div class="form-group row">
<label for="form_longish_text"
class="col-sm-2 col-form-label">Longish Text</label>
<div class="col-sm-7">
<textarea class="form-control" rows="3"
id="form_longish_text"
name="form_longish_text">{{ instruct.LongishText }}
</textarea>
</div>
</div>

Validating GET Params with WTForms in Flask

I have spent a couple of days trying to get WTForms to validate my request.args, but I just can not get form.validate() to return True.
The idea is that I have a simple text field for user input in a WTForm as shown below.
form.py
class SearchForm(FlaskForm):
q = StringField('q',
validators=[])
search = SubmitField('Search')
def validate_q(self, q):
if q.data not in allowed_values: #"allowed_values" is just a list I want to check against
raise ValidationError('')
search.html
<form method="GET" action="{{ url_for('finance.search') }}">
<div class="col-9 col-md-5 p-0 m-0">
{% if form.q.errors %} {{ form.q(class="form-control form-control-md is-invalid") }}
<div class="invalid-feedback">
{% for error in form.q.errors %}
<span>{{ error }}</span> {% endfor %}
</div>
{% else %} {{ form.q(class="form-control form-control-md") }} {% endif %}
</div>
<div class="col-2 col-md-2 p-0">
{{ form.search(class="btn btn-md btn-dark") }}
</div>
</form>
routes.py
#finance.route('/finance/search')
def search():
form = SearchForm(request.args)
print(form.validate()) #always gives false
The HTML code for the form is included on several templates and submitting the form always directs to the search route that is shown below. I tried following WTForms documentation and passed in request.args into the form. When I ran the .validate() on the object, the validate function for the q parameter also executed, but for some reason .validate() always returns False.
Can anyone please elaborate on why that might be? I know I can use post request, or add a custom validation function inside the route, but I want to avoid workarounds if possible.
(stack-overflow seems to use a similar type of architecture for their search http://127.0.0.1:8000/finance/search?q=aapl&search=Search vs https://stackoverflow.com/search?q=aapl and I want to follow that if possible.)
Thanks!
I actually just figured out that the error was happening because I was not including a crsf_token in the form. The token is not needed since the its a get request, but this needs to be explicitly stated with meta = {'csrf': False}.
#finance.route('/finance/search')
def search():
form = SearchForm(request.args, meta={'csrf': False})
print(form.validate()) #Now gives True if validation function does not raise error

Flask wtf-form override error message

Im using wtf-form for my password reset page but im having trouble with the generated error messages. I want to be able to either disable the error that wtf-form generates and use my own or be able to override them.
{% for e in form.password.errors %}
<span class="label label-danger">{{ e }}</span>
{% endfor %}
<form action="http://127.0.0.1:5000/setpassword" method=post>
<div class="form-group">
{{ form.csrf_token }}
{{ wtf.form_field(form.password, class='form-control', placeholder='Enter Password') }}
</div>
class PasswordForm(FlaskForm):
password = PasswordField('',validators=[pass_num, pass_small_lett, pass_big_lett, pass_special, pass_min])
confirm = PasswordField('',[validators.EqualTo('password', message='Not matching')])
submit = SubmitField('Send')
Don't use form_field() if you don't want its extra functionality. This will render just the input field:
{{ form.password(class='form-control', placeholder='Enter Password') }}
In your example the field's label is empty, but in case the field has a label, it must be printed as well. Bootstrap rules are:
Wrap labels and form controls in <div class="form-group"> (needed for optimum spacing)
Add class .form-control to all textual <input>, <textarea>, and <select elements
(source: https://www.w3schools.com/bootstrap/bootstrap_forms.asp)

Displaying custom checkboxes with Django and ManyToManyField

I'm having trouble wiring my checkboxes together with the template to make a good user experience. I'm sending my form to the view which contains the following MyType which is described as:
models
class A(models.Model):
MyType = models.ManyToManyField(P)
forms
MyType = forms.ModelMultipleChoiceField(queryset=P.objects.all(), required=False, widget=forms.CheckboxSelectMultiple)
and the view is being sent as:
return render(request, "static/about.html", {'form': form})
and in the template, I have the following kind of structure
<li class="option table">
<div class="cell" id="center">
<div class="option-text">Option 1</div>
</div>
<div class="cell" id="right">
<div class="option-checkbox">
<div class="check"></div>
<input id="myid1" name="myname1" value="1" type="checkbox">
</div>
</div>
Now I can use answer: On a django form, how do I loop through individual options per field? to loop through, but this only gives the description. How do I recreate the actual HTML that deals with the options in the smartest way?
I've tried the shell to look at the objects, but I really don't see what I should be pulling in. i.e. in the template to recreate. What fields of each option should I pull in? or how do I get at the right info to build up my HTML such that the saving aspect of the form will work.
In a template I cannot do:
{% for field in form["MySmoking"] %}
{% endfor %}
so how could I modify the following:
{% for option in form.MyType.field.choices.queryset %}
{{ option.image }}
{{ option.title }}
{{ option.description }}
{% endfor %}
so, how would I spit out the HTML that builds the checkbox.
Thanks.

Categories

Resources