How to custom the fields in flask-wtf forms - python

there.
I defined a class called LoginForm:
class LoginForm(FlaskForm):
email = StringField('Email', validators=[DataRequired(), Length(5, 64), Email()])
password = PasswordField('Password', validators=[DataRequired()])
remember_me = BooleanField('Remember me')
submit = SubmitField('Log In')
Then I render it in the templates login.html:
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{{ wtf.quick_form(form) }}
It looks like above:
And I think it is a little bit ugly. So, I render the fields in the normal way:
<form action="" method="POST" role="form" class="form">
{{ form.hidden_tag() }} {{ wtf.form_errors(form, hiddens="only") }}
<div class="form-group required">
<label class="control-label" for="email">Email</label>
{{ form.email(class="form-control",id="email",required="required",type="text",value="",placeholder="Enter email") }}
</div>
<div class="form-group required">
<label class="control-label" for="password">Password</label>
{{ form.password(class="form-control",id="password",required="required",type="password",value="",placeholder="Enter email") }}
</div>
<div class="checkbox">
<label>
<input id="remember_me" name="remember_me" type="checkbox" value="y"> Remember me
</label>
</div>
{{ form.submit(class="btn btn-success btn-block") }}
Register
</form>
It agrees with me now. But I met a problem: When I click the green login button, it does not validate the data(like the format of a email address). How should I solve this problem?

First I thought that it is not validating. Thank #BurhanKhalid for reminding me.
It's just not showing the errors. I changed the code and it works.
{% if form.email.errors %}
<div class="form-group required has-error">
{{ form.email(class="form-control",id="email",required='required',type="text",value="",placeholder="Enter email") }}
{% for error in form.email.errors %}
<p class="help-block">{{ error }}</p>
{% endfor %}
{% else %}
<div class="form-group required">
{{ form.email(class="form-control",id="email",required='required',type="text",value="",placeholder="Enter email") }}
{% endif %}
</div>

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.

flask wtforms showing validation error on 2 forms on the same html page and same route/view

So I have 2 forms, login form and registration form, on home.html, which show up as a modal when clicked on the login button, which looks like as follows:
home.html
<button type="button" class="btn btn-outline-danger" data-bs-toggle="modal" data-bs-target="#loginModal">
Login
</button>
<div class="modal fade" id="loginModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Sign In</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<button id="login" class="btn btn-login btn-md">Log In</button>
<button id="regis" class="btn btn-login btn-md">Register</button><hr style="margin-top: 0px;">
<div class="" id="login">
<form method="POST" action="">
{{ login_form.hidden_tag() }}
<fieldset class="form-group">
<div class="form-group">
{% if login_form.email_login.errors %}
{{ login_form.email_login(class="form-control form-control-md is-invalid") }}
<div class="invalid-feedback">
{% for error in login_form.email_login.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ login_form.email_login(class="form-control form-control-md mb-2", placeholder="Email") }}
{% endif %}
</div>
<div class="form-group">
{% if login_form.password_login.errors %}
{{ login_form.password_login(class="form-control form-control-md is-invalid") }}
<div class="invalid-feedback">
{% for error in login_form.password_login.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ login_form.password_login(class="form-control form-control-md mb-2", placeholder="Password") }}
{% endif %}
</div>
<div class="form-check">
{{ login_form.remember(class="form-check-input") }}
{{ login_form.remember.label(class="form-check-label") }}
</div>
</fieldset>
<div class="form-group">
{{ login_form.submit_login(class="btn btn-danger") }}
<small class="text-muted ml-2">
Forgot Password?
</small>
</div>
</form>
</div>
<div class="" id="regis">
<form method="POST" action="">
{{ regis_form.hidden_tag() }}
<fieldset class="form-group">
<div class="form-group">
{% if regis_form.username_regis.errors %}
{{ regis_form.username_regis(class="form-control form-control-md is-invalid") }}
<div class="invalid-feedback">
{% for error in regis_form.username_regis.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ regis_form.username_regis(class="form-control form-control-md mb-2", placeholder="Username") }}
{% endif %}
</div>
<div class="form-group">
{% if regis_form.email_regis.errors %}
{{ regis_form.email_regis(class="form-control form-control-md is-invalid") }}
<div class="invalid-feedback">
{% for error in regis_form.email_regis.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ regis_form.email_regis(class="form-control form-control-md mb-2", placeholder="Email") }}
{% endif %}
</div>
<div class="form-group">
{% if regis_form.password_regis.errors %}
{{ regis_form.password_regis(class="form-control form-control-md is-invalid") }}
<div class="invalid-feedback">
{% for error in regis_form.password_regis.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ regis_form.password_regis(class="form-control form-control-md mb-2", placeholder="Password") }}
{% endif %}
</div>
</fieldset>
<div class="form-group">
{{ regis_form.submit_regis(class="btn btn-danger") }}
</div>
</form>
</div>
</div>
</div>
</div>
</div>
The two "Log In" and "Register" buttons are tabs i.e. by clicking on "Register" button you will see registration form and vice versa which is controlled by JavaScript.
My forms.py file looks like this:
forms.py
class RegistrationForm(FlaskForm):
username_regis = StringField('Username',
validators=[DataRequired(), Length(min=2, max=20)])
email_regis = StringField('Email',
validators=[DataRequired(), Email()])
password_regis = PasswordField('Password', validators=[DataRequired()])
submit_regis = SubmitField('Sign Up')
class LoginForm(FlaskForm):
email_login = StringField('Email',
validators=[DataRequired(), Email()])
password_login = PasswordField('Password', validators=[DataRequired()])
remember = BooleanField('Remember Me')
submit_login = SubmitField('Login')
and below is the routes.py function:
routes.py
#app.route("/", methods=['GET', 'POST'])
def home():
login_form = LoginForm()
regis_form = RegistrationForm()
if request.method == 'POST':
if login_form.validate_on_submit() and login_form.submit_login.data:
user = User.query.filter_by(email=login_form.email_login.data).first()
if user and bcrypt.check_password_hash(user.password, login_form.password_login.data):
login_user(user, remember=login_form.remember.data)
next_page = request.args.get('next')
return redirect(next_page) if next_page else redirect(url_for('home'))
else:
flash('Login Unsuccessful. Please check email and password', 'danger')
if regis_form.validate_on_submit() and regis_form.submit_regis.data:
hashed_password = bcrypt.generate_password_hash(regis_form.password_regis.data).decode('utf-8')
user = User(username=regis_form.username_regis.data, email=regis_form.email_regis.data, password=hashed_password)
db.session.add(user)
db.session.commit()
flash('Your account has been created! You are now able to log in', 'success')
return redirect(url_for('home'))
return render_template('home.html', login_form=login_form, regis_form=regis_form)
Now, The problem is that when I try to login using an email that is not registered it flashes me
'Login Unsuccessful. Please check email and password'
which is ok as it should do this. but when I open the login modal again and open the registration tab, the * username_regis password_regis and email_regis* fields are showing me 'The field is required' error.
It should not show me this error on registration form because I never submitted this form.
I want to get rid of these error messages. I will deeply appreciate any help.
The code you wrote is as follows:
if request.method == 'POST':
if login_form.validate_on_submit() and login_form.submit_login.data:
<SNIP>
if regis_form.validate_on_submit() and regis_form.submit_regis.data:
<SNIP>
That means that both forms are are validated, no matter what form you submit. So when you submit one form the other will always show errors. The preferred solution is to post to different routes. On your form the action parameter will need to be filled and you will need two functions, one for each route. You than will also get rid of asking if form data is available. It must be, because you are on that route.

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

Django/Bootstrap template rendering

I'm currently coding a website using Django and Bootstrap.
I created the templates and models first, and now, I'm implementing the controllers. All is not implemented yet, but I needed some help on this. I was wondering how to render a Django authentication form with the Boostrap grid system. I'm using Boostrap 4 and Django 2.0.4. My older form was like this :
<div class="jumbotron">
<form class="form-horizontal" method="post" action="{% url 'login' %}">
{% csrf_token %}
<div class="form-group">
<div class="col-lg-4 offset-lg-4">
<label class="control-label" for="usernameInput">{{ form.username.label_tag }}</label>
<input id="usernameInput" type="text" name="{{ form.field.html_name }}" value="{{ form.username }}" class="form-control"
placeholder="Username">
</div>
</div>
<div class="form-group">
<div class="col-lg-4 offset-lg-4">
<label class="control-label" for="passwordInput">{{ form.password.label_tag }}</label>
<input id="passwordInput" type="password" name="{{ form.field.html_name }}" value="{{ form.field.value }}" class="form-control"
placeholder="Password">
</div>
</div>
<div class="container" id="btn_login">
<button type="submit" class="btn btn-primary btn-md" value="login" role="button">Log in</button>
<input type="hidden" name="next" value="{{ next }}"/>
</div>
</form>
<span class="container" id="forgotten_password">
Forgot your password ?
</span>
</div>
And here is the new one :
<div class="jumbotron">
{% load widget_tweaks %}
<form class="form-horizontal" method="post" action="{% url 'login' %}">
{% csrf_token %}
{% for hidden_field in form.hidden_fields %}
{{ hidden_field }}
{% endfor %}
{% if form.non_field_errors %}
<div class="alert alert-danger" role="alert">
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
{% for field in form.visible_fields %}
<div class="form-group">
{{ field.label_tag }}
{% if form.is_bound %}
{% if field.errors %}
{% render_field field class="form-control" %}
{% for error in field.errors %}
<div class="invalid-feedback">
{{ error }}
</div>
{% endfor %}
{% else %}
{% render_field field class="form-control" %}
{% endif %}
{% else %}
{% render_field field class="form-control" %}
{% endif %}
{% if field.help_text %}
<small class="form-text text-muted">{{ field.help_text }}</small>
{% endif %}
</div>
{% endfor %}
<div class="container" id="btn_login">
<button type="submit" class="btn btn-primary btn-md" role="button">Log in</button>
</div>
</form>
<span class="container" id="forgotten_password">
Forgot your password ?
</span>
</div>
But as you can obviously tell, this is not rendering the same way.
For example, I'd like to take back the width of the input.
For the rest, I use this line in my urls.py
re_path(r'^login/$', auth_views.login, {'template_name': 'main/login.html'}, name='login'),
And this one in my settings.py to get redirected to the right page :LOGIN_REDIRECT_URL = 'my_pls'
I googled a lot and finally used this link (in case you case notice something I didn't understand) : https://simpleisbetterthancomplex.com/article/2017/08/19/how-to-render-django-form-manually.html#understanding-the-rendering-process
You should use custom HTML attributes on your **forms.py**.
It's simple:
from django import forms
class your_form(forms.Form):
attrs_for_the_field = {
'class': 'form-control',
'placeholder': 'Write here!',
}
field = forms.CharField(widget=forms.CharField(attrs=attrs_for_the_field))
With this code you will render the following HTML:
<input type="text" name="field" class="form-control" placeholder="Write here!" id="id_field">
Take a look at https://docs.djangoproject.com/en/2.0/ref/forms/widgets/ in order to know how Django represents an HTML input element.
You also should read https://docs.djangoproject.com/en/2.0/ref/forms/fields/ so that you could understand how it works.
You can add all the HTML configuration for the fields in the form code in forms.py . Than the form can be displayed with just {{ form.as_p}}.
Width of input can be retrieved with jQuery or JavaScript .

Python, Flask Method Not Allowed

I am currently learning python and am trying to write an app. I have the basics done. I followed a tutorial which was helpful but have gotten stuck. My understanding is 100% up to scratch yet so any help and reasoning behind it would be great.
I am getting a Method Not Allowed Error when trying to submit a form. I will post the code below and hopefully someone can help.
new_action.py
{% extends "base.html" %}
{% block content %}
<h2>New Action Request</h2>
{% include 'flash.html' %}
<div class="well">
<form class="form-horizontal" action="" method="post" name="post">
{{ form.hidden_tag() }}
<div class="control-group{% if form.errors %} error{% endif %}">
<label class="pull-right" for="post">Date: {{
datetime.date(datetime.utcnow()) }}</label>
<div class="controls">
{{form.timestamp}}
</div>
<label class="control-label" for="post">Raised By:</label>
<div class="controls">
{{ form.raised_by }}
{% for error in form.errors.post %}
<span class="help-inline">[{{ error }}]</span><br>
{% endfor %}
</div>
<br/>
<label class="control-label" for="post">Source:</label>
<div class="controls">
{{ form.source }}
{% for error in form.errors.post %}
<span class="help-inline">[{{ error }}]</span><br>
{% endfor %}
</div>
<br/>
<label class="control-label" for="post">Category:</label>
<div id="radios1" class="controls" data-toggle="buttons-radio">
<button type="button" class="btn active" name="health_safety" value="health_safety">Health &
Safety</button>
<button type="button" class="btn" name="quality" value="quality">Quality</button>
<input type="hidden" name="category" value={{request.form['category']}} />
</div><br/>
<br/>
<label class="control-label" for="post">Sub-Category:</label>
<div class="controls">
{{ form.sub_category }}
{% for error in form.errors.post %}
<span class="help-inline">[{{ error }}]</span><br>
{% endfor %}
</div>
<br/>
<label class="control-label" for="post" width="80%" >Description:</label>
<div class="controls" >
{{ form.issue }}
{% for error in form.errors.post %}
<span class="help-inline">[{{ error }}]</span><br>
{% endfor %}
</div>
<br/>
<label class="control-label" for="post">Immediate Action:</label>
<div class="controls">
{{ form.immediate_action }}
{% for error in form.errors.post %}
<span class="help-inline">[{{ error }}]</span><br>
{% endfor %}
</div>
</div>
<div class="control-group">
<div class="controls">
<input class="btn btn-primary" type="submit" value="Submit Action Request">
</div>
</div>
</form>
</div>
{% endblock %}
Views.py
#app.route('/new_action', methods=['GET', 'SET'])
#login_required
def new_action():
form = ActionRequestForm()
if request.method == 'POST':
return redirect(url_for('index'))
#actionrequest = ActionRequest(id = form.id.data, category = form.category.data)
'''post = Post(body=form.post.data, timestamp=datetime.utcnow(),
author=g.user)
db.session.add(post)
db.session.commit()
flash('Your post is now live!')'''
return render_template('new_action.html',
user = user,
form = form,
datetime = datetime
)
Your form is trying to POST data to the server. This is a HTTP POST request. You define GET and SET methods in your view. You need to use POST there.
#app.route('/new_action', methods=['GET', 'POST']) # Changed SET to POST here
#login_required
def new_action():
# ... what ever...
You should go through this RFC for HTTP. There is no SET-method.
#app.route('/new_action', methods=['GET', 'SET'])
This line of code is only allowing "GET" and "SET" methods, while you are trying to "POST" to that route.

Categories

Resources