Python Flask WTForms custom validator does not work - python

I am trying to create a custom URL validator for a form input field but validator does not seem to work. I applied a DataRequired() to the StringField which works fine but the custom validator does not. Here is code:
def validate_domain(form, field):
url_regex = r'''((http|https)\:\/\/)?[a-zA-Z0-9\.\/\?\:#\-_=#]+\.
([a-zA-Z]){2,6}([a-zA-Z0-9\.\&\/\?\:#\-_=#])*'''
regex = re.compile(url_regex)
url = regex.match(field.data).group()
if url:
field.data = 'http://' + urlparse(url).netloc
else:
raise ValidationError('Please enter a valid URL.')
class SubmitDomainForm(Form):
domain = StringField('Domain', validators=[DataRequired(),
validate_domain])
submit = SubmitField('Crawl')
HTML for the same:
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<form method="POST" action="">
{{ form.hidden_tag() }}
<fieldset class="form-group">
<legend class="border-bottom mb-4">{{ legend }}</legend>
<div class="form-group">
{{ form.domain.label(class="form-control-label") }}
{% if form.domain.errors %}
{{ form.domain(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.domain.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.domain(class="form-control form-control-lg") }}
{% endif %}
</div>
</fieldset>
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
</div>
</form>
{% endblock content %}
Even when I submit a non URL input the form just submits. I am unable to get what seem to going wrong here.

After testing my comment it seems that multi-lining your regex is likely causing you an issue
import re
from urllib.parse import urlparse
data = 'https://google.com/#resource'
url_regex = r'''((http|https)\:\/\/)?[a-zA-Z0-9\.\/\?\:#\-_=#]+\.([a-zA-Z]){2,6}([a-zA-Z0-9\.\&\/\?\:#\-_=#])*'''
regex = re.compile(url_regex)
url = regex.match(data).group()
print('http://' + urlparse(url).netloc)
the above code works for me.

Related

For some FlaskForm validators, error message does not pop up

While "DataRequired" and "NumberRange" produce a pop-up error message, "EqualTo" (and my own custom "MoreThan") do not. How can I get them to also produce a pop-up message?
####################################################################
My form has three fields. "min_nFeatures" has to be lower than "max_nFeatures", so I changed the code of the "EqualTo" validator to "MoreThan" (as advised in Code a validator for a WTForms form which compares the inputs from two fields). The validator is working: if the user enters a larger value for "min_nFeatures", it does not go through (it returns to the same page). However, there is no pop-up message as with the other built-in validators (for example, if the user does not enter anything, there's a pop-up "Please fill out this field"). I would like the same behavior for the custom validator.
My code:
class MoreThan(object):
def __init__(self, fieldname, message=None):
self.fieldname = fieldname
self.message = message
def __call__(self, form, field):
try:
other = form[self.fieldname]
except KeyError:
raise ValidationError(field.gettext("Invalid field name '%s'.") % self.fieldname)
if field.data <= other.data:
d = {
'other_label': hasattr(other, 'label') and other.label.text or self.fieldname,
'other_name': self.fieldname
}
message = self.message
if message is None:
message = field.gettext(
'The maximal number of expressed genes has to be larger than the minimal number of expressed genes')
raise ValidationError(message)
class vln_plot_form(FlaskForm):
min_nFeatures = IntegerField('* Minimal number of expressed genes:', validators=[DataRequired()])
max_nFeatures = IntegerField('* Maximal number of expressed genes:',
validators=[DataRequired(), MoreThan('min_nFeatures')])
max_mtpercent = IntegerField('Maximal percent of mitochondrial gene expression:', validators=[NumberRange(1, 100)])
submit = SubmitField('Submit')
view:
#app.route('/vln', methods=['POST', 'GET'])
def violin_plots():
...
form = vln_plot_form()
if request.method == 'POST':
if form.validate_on_submit():
...
return redirect(url_for('next_page'))
return render_template('violin_plots.html', form=form)
I read Message not flashing on some WTForm validation methods but could not apply it to my case.
########################################################################
edit:
Here's my HTML code:
{% extends 'base.html' %}
{% block head %}
<title>HELLO</title>
{% endblock %}
{% block body %}
<center><h1>Run!</h1></center>
<h2>Step 2/3</h2>
<figure>
<img src={{ vln_plot_file }} align="middle" alt="vln_plot" style="width:70%">
</figure>
<form method="POST" action="" enctype="multipart/form-data">
{{ form.hidden_tag() }}
<div class="form-horizontal">
<div class="form-group col-md-6">
{{ form.csrf_token() }}
<label for=""> {{ form.min_nFeatures.label }}</label>
{{ form.min_nFeatures }}
</div>
<div class="form-group col-md-6">
{{ form.csrf_token() }}
<label for=""> {{ form.max_nFeatures.label }}</label>
{{ form.max_nFeatures }}
</div>
{% if with_mt %}
<div class="form-group col-md-6">
{{ form.csrf_token() }}
<label for=""> {{ form.max_mtpercent.label }}</label>
{{ form.max_mtpercent }}
</div>
{% endif %}
<div class="form-group">
{{ form.csrf_token() }}
{{ form.submit(class="btn btn-primary")}}
</div>
</div>
{% if form.errors %}
{{ form.errors }}
{% endif %}
</form>
{% endblock %}
Since I added "{{ form.errors }}" at the end of the HTML, I do see the correct form.errors at the bottom of the webpage, but this is of course a very ugly way to display the errors...
This should do the trick:
<form method="POST" action="" enctype="multipart/form-data">
{{ form.hidden_tag() }}
<div class="form-horizontal">
<div class="form-group col-md-6">
{{ form.csrf_token() }}
<label for=""> {{ form.min_nFeatures.label }}</label>
{{ form.min_nFeatures }}
{% for error in form.min_nFeatures.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</div>
<div class="form-group col-md-6">
{{ form.csrf_token() }}
<label for=""> {{ form.max_nFeatures.label }}</label>
{{ form.max_nFeatures }}
{% for error in form.max_nFeatures.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</div>
{% if with_mt %}
<div class="form-group col-md-6">
{{ form.csrf_token() }}
<label for=""> {{ form.max_mtpercent.label }}</label>
{{ form.max_mtpercent }}
{% for error in form.max_mtpercent.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</div>
{% endif %}
<div class="form-group">
{{ form.csrf_token() }}
{{ form.submit(class="btn btn-primary")}}
</div>
</div>
</form
Note how I manage the display of errors for each of the fields individually.

Difficulties with having a Flask-wtforms in the main layout template of the website

I have been working on a website for the past while using flask, python and bootstrap. I added footer to my website and I would like a "contact me" form in the footer. Below is my current code for creating a contact form and displaying this form.
class ContactForm(FlaskForm):
email = StringField("", validators=[DataRequired(), Email()])
content = TextAreaField("", validators=[DataRequired(), length(min=2, max=500)])
submit = SubmitField("Submit")
<form method="POST" action="">
<div class="form-group">
{{ form.email.label(class="form-control-label") }}
{% if form.email.errors %}
{{ form.email(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.email.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.email(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-group">
{{ form.content.label(class="form-control-label") }}
{% if form.content.errors %}
{{ form.content(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.content.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.content(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
</div>
</form>
I created this footer in my "layout.html" file which is the base file that all other html documents extend from so that all pages will have this same footer.
The issue is that then I need to create and instance of this form object on every page of my application and handle the validate_on_submit on every page. (Sample code below)
#app.route("/random_page", methods=["GET", "POST"])
def random_page():
form = ContactForm()
if form.validate_on_submit():
# code to send the email goes here
return render_template("random_page")
I am looking for an easier way to do this so that I don't need to repeat code on every page and so that It can be considerably simpler. I am not very experienced with flask and would highly appreciate the help.
Use the #app.context_processor decorator, documentation to inject an instance of your contact form into the template context for all templates. For example:
#app.context_processor
def inject():
return dict(
contact_form=ContactForm(),
)
Then re-write the form's HTML to use the contact_form template variable, also adding in an appropriate action route.
<form method="POST" action="{{url_for('contact')}}">
<div class="form-group">
{{ contact_form.email.label(class="form-control-label") }}
{% if contact_form.email.errors %}
{{ contact_form.email(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.email.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ contact_form.email(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-group">
{{ contact_form.content.label(class="form-control-label") }}
{% if contact_form.content.errors %}
{{ contact_form.content(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in contact_form.content.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ contact_form.content(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-group">
{{ contact_form.submit(class="btn btn-outline-info") }}
</div>
</form>
Then add a route to solely handle this form's postback:
#app.route("/contact", methods=["POST"])
def contact():
form = ContactForm()
if form.validate_on_submit():
# code to send the email goes here
# blah blah

How to display the field "label" in django's builtin password validation errors?

I am using Django's builtin authentication class views and need to customize the error message displayed when password validation fails.
For example, in the builtin PasswordResetView, if I try to change my password to test, the following errors will display in my template:
new_password2
This password is too short. It must contain at least 8 characters.
This password is too common.
I would like to change new_password2 to New Password.
Here is the relevant part of my template for the PasswordResetView:
{% extends 'registration/base.html' %}
{% block card_body %}
<div class="form-group">
<label for="old_password">
Old Password:
{{ form.old_password }}
</label>
</div>
<div class="form-group">
<label for="new_password1">
New Password:
{{ form.new_password1 }}
</label>
</div>
<div class="form-group">
<label for="new_password2">
Confirm Password:
{{ form.new_password2 }}
</label>
</div>
<div class="form-group">
<input type="submit" value="Change" class="btn float-right login_btn">
</div>
{% endblock card_body %}
{% block card_footer %}
{% if form.errors %}
<p class="d-flex justify-content-center links">
{{ form.errors }}
</p>
{% endif %}
{% endblock card_footer %}
Supply some dict to the template that will map form field names to labels you want like:
fields_mapping = {
'old_password': 'Old password',
'new_password1': 'New password',
'new_password2': 'Confirm password'
}
Just manually iterate over errors and use the the mapping dic to convert field names to labels you want:
{% for field_name in form.errors: %}
{{ fields_mapping[field_name] }}:
{% for err_message in form.errors[field_name]: %}
* {{ err_message }}
{% endfor %}
{% endfor %}
Customize HTML/CSS there as you want

WTF form.validate_on_submit() is not working while updating a form

I have designed a form to update a task. The form will get the details of the task into the update form and while submitting that form the form.validate_on_submit does not work and redirects again to the same form with the previous values. Here are my routes and template code. I have used {{ form.csrf_token }}. But similar logic works with the newTask route.
Update Route
#main.route('/update/<int:id>',methods=['POST','GET'])
#login_required
def update(id):
if form.validate_on_submit():
t.task_name = form.task_name.data
t.assignee = form.assignee.data
t.deal_size = form.deal_size.data
t.lead_status = form.lead_status.data
t.stage = form.stage.data
t.priority = form.priority.data
t.submission_date = form.date.data
db.session.add(t)
db.session.commit()
return redirect('/')
form = NewTaskForm()
t = Tasks.query.get_or_404(id)
return render_template("update.html",form = form , t = t)
update.html
<form method="POST" action="/update/{{ t.id }}">
{{ form.csrf_token }}
<div class="form-group">
{{ form.task_name.label }}{{ form.task_name(class='form-control',value = t.task_name ) }}
{% if form.task_name.errors %}
{% for error in form.task_name.errors %}
<div class="alert alert-danger" role="alert">
{{ error }}
</div>
{% endfor %}
{% endif %}
</div>
<div class="form-group">
{{ form.assignee.label }}{{ form.assignee(class='form-control',value = t.assignee) }}
{% if form.assignee.errors %}
{% for error in form.assignee.errors %}
<div class="alert alert-danger" role="alert">
{{ error }}
</div>
{% endfor %}
{% endif %}
</div>
<div class="form-group">
{{ form.deal_size.label }}{{ form.deal_size(class='form-control',value = t.deal_size) }}
{% if form.deal_size.errors %}
{% for error in form.deal_size.errors %}
<div class="alert alert-danger" role="alert">
{{ error }}
</div>
{% endfor %}
{% endif %}
</div>
<div class="form-group">
{{ form.lead_status.label }}{{ form.lead_status(class='form-control',value = t.lead_status) }}
{% if form.lead_status.errors %}
{% for error in form.lead_status.errors %}
<div class="alert alert-danger" role="alert">
{{ error }}
</div>
{% endfor %}
{% endif %}
</div>
<div class="form-check padding-none">
{{ form.stage.label(class='form-check-label') }}{{ form.stage(class='form-check-input customRadio',value = t.stage.data) }}
{% if form.stage.errors %}
{% for error in form.stage.errors %}
<div class="alert alert-danger" role="alert">
{{ error }}
</div>
{% endfor %}
{% endif %}
</div>
<div class="form-group">
{{ form.date.label }}{{ form.date(class='form-control',value = t.submission_date.date()) }}
{% if form.date.errors %}
{% for error in form.date.errors %}
<div class="alert alert-danger" role="alert">
{{ error }}
</div>
{% endfor %}
{% endif %}
</div>
{{ form.submit(class="btn btn-outline-primary btn-block") }}
</form>
newTask route
#main.route('/new-task',methods=['POST','GET'])
#login_required
def newTask():
form = NewTaskForm()
if form.validate_on_submit():
t = Tasks()
t.task_name = form.task_name.data
t.assignee = form.assignee.data
t.deal_size = form.deal_size.data
t.lead_status = form.lead_status.data
t.stage = form.stage.data
t.priority = form.priority.data
t.submission_date = form.date.data
db.session.add(t)
db.session.commit()
return redirect('/')
return render_template("newtask.html",form = form)
If you want to redirect the user to the new form use:
#main.route('/update/<int:id>',methods=['POST','GET'])
#login_required
def update(id):
if form.validate_on_submit():
# do something and then redirect
return redirect(url_for('confirm'))
return render_template("update.html",form = form , t = t)
Probably, you need to create a new function for the 'confirmation site' if you haven't done already.
You can look up the flask documentation if you want to have a better understanding of
url_for :
It accepts the name of the function as its first argument and any number of keyword arguments, each corresponding to a variable part of the URL rule. Unknown variable parts are appended to the URL as query parameters.
Here you can find a good explanation as well:
create-dynamic-urls-in-flask

Flask button generated with wtforms not triggering POST request

I'm writing a Flask application that works sort of like a blog. I generated the text boxes with wtforms, but whenever I click on the button to save the text, nothing happens. I get no errors.
I've tried adding print statements to my code, and everything is working fine except that Flask is not receiving the POST request. Clicking the button does absolutely nothing.
This is the code for the endpoint. I added a print() statement after if request.method but nothing printed out, so I'm assuming it's not receiving the POST request:
#app.route("/newtext", methods=["GET", "POST"])
#login_required
def newtext():
form = NewPost()
if request.method == "POST" and form.validate():
new_post = Texts(user_id=current_user.id, title=form.title.data, content=form.content.data)
db.session.add(new_post)
db.session.commit()
new_click = UserActions(user_id=current_user.id, action=4)
db.session.add(new_click)
db.session.commit()
last_text = db.session.query(Texts).order_by(Texts.id.desc()).first()
text_id = last_text.id
text_version = TextVersions(content=form.content.data, user_id=current_user.id, text_id=text_id)
db.session.add(text_version)
db.session.commit()
plaintext = BeautifulSoup(form.content.data)
text_summary = Grammar.summary(plaintext.get_text())
return render_template("summary.html", text_id=text_id, text_summary=text_summary)
else:
return render_template("basiceditor.html", form=form)
And this is the html code for the webpage. It renders okay (the GET method clearly works) but the button does nothing:
<div class="container mt-5">
<div class="content-section">
<form method="POST" action="/newtext">
{{ form.hidden_tag() }}
<fieldset class="form-group">
<legend class="border-bottom mb-4">{{ legend }}</legend>
<div class="form-group">
{{ form.title.label(class="form-control-label") }}
{% if form.title.errors %}
{{ form.title(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.title.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.title(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-group">
{{ form.content.label(class="form-control-label") }}
{% if form.content.errors %}
{{ form.content(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.content.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.content(class="form-control form-control-lg", id="controleditor") }}
{% endif %}
</div>
</fieldset>
<div class="form-group">
{{ form.submit(class="btn btn-success") }}
</div>
</form>
</div>
</div>
The app reports no errors. It should save the text to the database and send the person to the summary.html page, but clicking on the button does nothing. I am completely stumped.

Categories

Resources