WTForms: Disable client-side validation on cancel - python

What I'm asking for is actually quite simple. I want to create a form with some fields and a submit and a cancel button. I want to use the quick_form template function of Flask-Bootstrap to keep overhead in my template low. My form looks like this:
from flask_wtf import FlaskForm
from wtforms.validators import Required, Length
class SequenceForm(FlaskForm):
name = StringField('Name:', validators=[Required(), Length(1, 128)])
# some other fields here
submit = SubmitField('submit')
cancel = SubmitField('cancel')
The template:
{% extends 'base.html' %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block content %}
<div class="container">
<form method="POST">
<div class="row">
<div class="col-xs-12">
{{ wtf.quick_form(form, button_map={'submit': 'primary'}) }}
</div>
</div>
</form>
</div>
{% endblock %}
As one would suspect I want to validate and accept the input values on submit and skip validation on cancel. So my view function looks as expected.
#main.route('sequence/', methods=['GET', 'POST'])
def sequence():
form = SequenceForm()
if request.method == 'POST':
if 'submit' in request.form:
if form.validate_on_submit():
print(form.duration.data)
else:
return redirect(url_for('main.index'))
return render_template('sequence.html', form=form)
Now if cancel is pressed there should logically be no validation and the redirect should take place. However I run into the issue that my view function doesn't even get called if I press either submit or cancel due to the client-side validation.
<input class="form-control" id="name" name="name" required="" type="text" value="">
Is there a way to disable client-side validation on WTForms?

Since you use Flask-Bootstrap's quick_form() macro, you can just set novalidate parameter to True to disable client-side validation (it will set a novalidate attribute to your HTML <form> element):
{{ wtf.quick_form(form, novalidate=True) }}
If you are using Bootstrap-Flask, the method is similar:
{{ render_form(form, novalidate=True) }}

The Required validator as well as the DataRequired and InputRequired which replace Required since version 3 of WTForms set the replace flag of the field. This flag is used to add the required attribute to the HTML representation of the field. My workaround is to manually create a validate function.
from wtforms.validators import ValidationError
def _required(form, field):
if not field.raw_data or not field.raw_data[0]:
raise ValidationError('Field is required')
class SequenceForm(FlaskForm):
name = StringField('Name:', validators=[_required, Length(1, 128)])
# some other fields here
submit = SubmitField('submit')
cancel = SubmitField('cancel')
This way there is no validation on the client-side and it is ensured that the view function is called on every submit or cancel.
Note
An even simpler solution is to subclass the InputRequired validator and overwrite the field_flags dictionary.
from wtforms.validators import InputRequired
class MyInputRequired(InputRequired):
field_flags = ()
class SequenceForm(FlaskForm):
name = StringField('Name:', validators=[MyInputRequired(), Length(1, 128)])

You could forbid rendering of the required attr.
class MyTextInput(wtforms.widgets.TextInput):
def __call__(self, field, **kwargs):
kwargs['required'] = False
return super().__call__(field, **kwargs)
For Python2 add args like this:super(MyTextInput, self)
and then:
name = StringField('Name:', validators=[Required(), Length(1, 128)], widget=MyTextInput())

To disable client-side form validation, add the 'novalidate' attribute to the HTML <form> element in your template:
<form method="POST" novalidate>

Related

Custom validation for WTForm fileds in Flask

I have used the built-in validators, but none of them is printing a message on the page. Also, I want to create a custom validator to check duplicate username. I have written the function, as I am a beginner, I don't know how to use it. Pls resolve the problem.
from flask import Flask, app, render_template, request, url_for
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField
from wtforms.validators import InputRequired, EqualTo, ValidationError
app = Flask(__name__)
app.config['SECRET_KEY'] = "PQrs12t46uvvrty567"
class MyForm(FlaskForm):
username = StringField('Username', validators=[InputRequired(message="This field is required.")])
password=PasswordField('Password', validators=[InputRequired(message=("enter the password"))])
confirm_password=PasswordField('Confirm Password', validators=[EqualTo('password')])
submit = SubmitField('Register')
def isDuplicate():
appdatabase={"Alis":"Smith","Mike":"Brown","Paul":"Miller"}
form = MyForm()
for user in appdatabase:
if form.username == appdatabase[user]:
raise ValidationError("Username already exists! Please choose another one.")
#app.route('/')
def base():
form = MyForm()
return render_template('customvalidator.html', form = form)
#app.route('/submitform', methods=["GET","POST"])
def submitform():
form = MyForm()
if form.validate_on_submit():
return 'Form accepted successfully.'
else:
return 'Incorrect form data'
if __name__=="__main__":
app.run(debug=True)
HTML file
<!DOCTYPE html>
<html>
<head><title>My website</title></head>
<body>
<h1>Registration form</h1>
<form action="{{ url_for('submitform') }}" method="post">
{{ form.csrf_token }}
{{ form.username.label }}
{{ form.username }}
<ul>
{% for error in form.username.errors %}
<li style="color: red;">{{ error }} </li>
{% endfor %}
</ul>
<br>
{{ form.password.label }}
{{ form.password }} <br><br>
{{ form.confirm_password.label }}
{{ form.confirm_password }} <br><br>
{{ form.submit}}
</form>
</body>
</html>
Try changing the isDuplicate function to this:
def isDuplicate(form, field):
appdatabase={"Alis":"Smith","Mike":"Brown","Paul":"Miller"}
form = MyForm()
for user in appdatabase:
if form.username.data == appdatabase[user]:
raise ValidationError("Username already exists! Please choose another one.")
Notice the added form and field parameters to the function to allow its use as a custom validator. form.username was also changed to form.username.data to access the data of the username field. (This whole function will need to be defined before the form.)
You will then need to add this custom validator to your list of validators for the username field in your form like so:
username = StringField('Username', validators=[InputRequired(message="This field is required."),isDuplicate])
With these changes, the custom validation should work, but we still haven't solved the issue of the custom error messages. To do this, add a novalidate attribute to your HTML form tag. Adding this will disable the default validation on forms and allow you to display custom messages.
With that in place, I believe the messages still won't work because of how you are handling the form submission. When the form does not validate, you will want to display the same form template instead of showing them whether it submitted or not.
My personal opinion is that merging your two form routes will be the simplest option. Have a route define the form, check for when it gets validated or not, and then render the form template. This will enable you to keep everything together and not render a different template when you only want to display the same template with a few added error messages.
Credits:
Custom validators (check the last point)
SO question on displaying validation messages
-which led me to this one on actually how to disable the default messages.

Flask-WTF forcing to fill all the fields even tho they're not rendered in the template

I'm trying to send the data to the current URL with post method, but "FinalForm" is failing validation because not all the fields are filled when I've only rendered two fields into the template from the form. Is there any way to overcome this?
#...
class MotherboardForm(FlaskForm):
chipset_model = StringField("ჩიპსეტი", validators=[DataRequired()])
socket_type = StringField("სოკეტი", validators=[DataRequired()])
ram_technology = StringField("მეხსიერების ტიპი", validators=[DataRequired()])
class RamForm(FlaskForm):
frequency = StringField("სიხშირე", validators=[DataRequired()])
size = StringField("მეხსიერების მოცულობა", validators=[DataRequired()])
class FinalForm(ItemForm, BasicsForm, CpuForm, GpuForm, MotherboardForm, RamForm):
submit = SubmitField("done")
In html I'm only rendering these fields
<form action="" method="post" novalidate>
{{ form.hidden_tag() }}
{{ form.frequency.label }}
{{ form.frequency() }}
{{ form.size.label }}
{{ form.size() }}
{{ form.submit() }}
</form>
This is the view function:
#app.route("/list_item", methods=["GET", "POST"])
#login_required
def list_item():
item = FinalForm()
if item.validate_on_submit():
return redirect(url_for("list_cpu"))
return render_template("add_item.html", form=item)
Help would be appreciated
If a field is set with validator DataRequired, it will always asks for input whether the field is rendered or not. The option would be to remove DataRequired validator or if the error still arise, use Optional validator.
Or you could fill the formdata with dummy value before validating it.

Flask WTF (Python) Size-varying list of lists of fields not working (unbound error)

I'm trying to make a uncertainty calculator, where I need a variable number of Fields (this is the final idea). However, while testing for number of 1 field of fields, I came across a issue. Instead of the fields being rendered in the page, there is only some random code in its place:
I tried checking if the issue was related to them having the same name "values" or something, but it seems that is not the issue. I don't know what to try anymore.
forms.py
from flask.ext.wtf import Form
from wtforms import StringField, BooleanField, DecimalField
from wtforms.validators import DataRequired
class Receiver(Form):
expression = StringField('expression', validators=[DataRequired()])
# ve_list = [[StringField('expreson'), DecimalField('expression', places=10)], [StringField('expreson'), DecimalField('expression', places=10)]]
# remember_me = BooleanField('remember_me', default=False)
ve_list = [[DecimalField('value', validators=[DataRequired()]), DecimalField('value', validators=[DataRequired()])]]
The views.py:
from flask import render_template, flash, redirect
from app import app
from .forms import Receiver
#app.route('/login', methods=['GET', 'POST'])
def login():
form = Receiver()
return render_template('request.html',
title='Calculate',
form=form)
request.html:
{% block content %}
<h1>Error propagation calculator</h1>
<form action="" method="post" name="login">
{{ form.hidden_tag() }}
<p>
Please enter the expression:<br>
{{ form.expression }}<br>
</p>
Enter the value and respective error:<br>
{% for ve in form.ve_list %}
{{ ve[0] }} +/- {{ ve[1] }}<br>
{% endfor %}
<p><input type="submit" value="Calculate"></p>
</form>
{% endblock %}
Fields are classes and need to be called in order to run their call method which will render the html to your page.
Example 1:
{{ form.expression() }}
Your field is rendering but it is best to call the field properly.
Edited:
Your list of fields is not going to work because you need to have an instantiated class attached to the form class attribute. When you load up the field like this it is an UnboundField.
I recommend adding fields dynamically in your view. You can see an answer to that problem here.
The fields that are part of the form need to be class variables of the form class. If they are not, then WTForms is not going to find them, so they are never bound to the form.
If you want to add a list of fields, you can do so by setting the attributes on the Form class. Something like this:
class Receiver(Form):
expression = StringField('expression', validators=[DataRequired()])
setattr(Receiver, 've_list0', DecimalField('value', validators=[DataRequired()]))
setattr(Receiver, 've_list1', DecimalField('value', validators=[DataRequired()]))
Then in the template, you can iterate on the form fields to render them, instead of rendering them one by one.

Ensure this value has at most %(limit_value)d characters (it has %(show_value)d). -Django error

"Ensure this value has at most %(limit_value)d characters (it has
%(show_value)d)."
This is the error i get in my django form that i rendered using html when i post the data. I have defined no restrictions on my fields.
Form :
author = 'PRAYAS'
from django import forms
from login.models import UserProfile
class loginform(forms.ModelForm) :
username = forms.CharField()
password = forms.CharField(widget=forms.PasswordInput())
fullname = forms.CharField(max_length=256)
class Meta :
model = UserProfile
fields = ('username','password','fullname')
<body>
{% if registered %}
<h1>Thanks For Registering!</h1>
{% else %}
<h1>Create new Account</h1>
<form id="form" method="post" action="/login/register/"
enctype="multipart/form-data">
{% csrf_token %}
<!-- Display each form. The as_p method wraps each element in a paragraph
(<p>) element. This ensures each element appears on a new line,
making everything look neater. -->
{{ form.as_p }}
<!-- Provide a button to click to submit the form. -->
<input type="submit" name="submit" value="Register" />
</form>
{% endif %}
</body>
</html>
def Register(request):
registered =False
if (request.method=='POST') :
user_form = loginform(data=request.POST)
try :
user = user_form.save()
user.set_password(user.password)
user.save()
registered=True
except :
print user_form.errors
return render(request,'login/register.html',{'form':user_form,'registered':registered,'slug':''})
if(request.method=='GET') :
user_form = loginform()
return render(request,'login/register.html',{'form':user_form,'registered':registered,'slug':''})
`
Never, ever, ever do a blank except.
Django forms have a means of triggering validation: form.is_valid(). You must always call that before attempting to call save. This is fully documented and there is no reason not to follow the exact pattern given there.

WTforms form not submitting but outputs no validation errors

I'm trying to get file uploads with flask-uploads working and running in to some snags. I'll show you my flask view function, the html and hopefully someone can point out what I'm missing.
Basically what happens is that I submit the form and it fails the if request.method == 'POST' and form.validate(): check in the view function. It jumps down to display the template. wtforms isn't kicking me any errors on the form so I'm wondering why its failing that if statement.
What am I over looking?
Setting up flask-uploads:
# Flask-Uploads
photos = UploadSet('photos', IMAGES)
configure_uploads(app, (photos))
View:
def backend_uploadphoto():
from Application import photos
from Application.forms.backend import AddPhotoForm
clients = Client.query.all()
events = Event.query.order_by('date').all()
form = AddPhotoForm(request.form, csrf_enabled=True)
if request.method == 'POST' and form.validate():
from uuid import uuid4
uuid = uuid4()
filename = '{0}.jpg'.format(uuid)
photo = Photo(uid=uuid, client=request.form['client'], event=request.form['event'])
photofile = photos.save(request.files.get('photo'), photo.filename)
return redirect(url_for('backend'))
return render_template('backend/addphoto.html', form=form, clients=clients, events=events)
Form:
class AddPhotoForm(Form):
photo = FileField('Photo')
client = IntegerField('Client:')
event = IntegerField('Event:')
HTML:
<form action="{{url_for('backend_uploadphoto')}}" method="post">
<p>
{{form.client.label}}
<select name="client">
{% for client in clients %}
<option value="{{client.id}}">{{client.fullname}}</option>
{% endfor %}
</select>
{{form.client.errors}}
</p>
<p>
{{form.event.label}}
<select name="event">
{% for event in events %}
<option value="{{event.id}}">{{event.name}}</option>
{% endfor %}
</select>
{{form.event.errors}}
</p>
<p><label for="photo">Photo:</label>{{form.photo}} <input type="submit" value="Upload"> {{form.photo.errors}}</p>
</form>
You have csrf_enabled=True but your form doesn't have any CSRF protection since you aren't inheriting from SecureForm. If you want to enable CSRF, read the documentation and update your form definition.
If this was unintended, you can remove csrf_enabled=True and your logic will work as expected.
To enable CSRF protection, there are a few steps:
Inherit from SecureForm
Create the generate_csrf_token and validate_csrf_token methods in your form. These methods will generate a unique key and raise errors when it doesn't validate.
Add {{ form.csrf_token }} to your template.

Categories

Resources