How to add Bootstrap Validation to WTForms - python-3.x

I am using WTForms in conjunction with Flask and I would like to integrate the Bootstrap Form Validation for errors in my form. I have a basic login form setup something like this:
class LoginForm(FlaskForm):
"""Login form."""
email = EmailField(
"Email Address", validators=[DataRequired(), Email(), Length(min=6, max=40)]
)
password = PasswordField(
"Password", validators=[DataRequired()]
)
def __init__(self, *args, **kwargs):
"""Create instance."""
super(LoginForm, self).__init__(*args, **kwargs)
self.user = None
def validate(self):
"""Validate the form."""
initial_validation = super(LoginForm, self).validate()
if not initial_validation:
return False
self.user = User.query.filter_by(email=self.email.data).first()
if not self.user:
self.email.errors.append("Unknown email address!")
return False
if not self.user.check_password(self.password.data):
self.password.errors.append("Invalid password!")
return False
if not self.user.verified:
self.email.errors.append("Please verify your email address!")
return False
return True
My login.html template is setup like this:
<form method="POST" action="{{ url_for('public.login') }}" role="form">
{{ form.csrf_token }}
<div class="form-group">
{{ form.email.label(class_="form-control-label") }}
<div class="input-group input-group-merge">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-user"></i></span>
</div>
{{ form.email(placeholder="name#example.com", class_="form-control") }}
</div>
</div>
<div class="form-group mb-4">
<div class="d-flex align-items-center justify-content-between">
<div>
{{ form.password.label(class_="form-control-label") }}
</div>
<div class="mb-2">
Lost password?
</div>
</div>
<div class="input-group input-group-merge">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-key"></i></span>
</div>
{{ form.password(placeholder="Password", class_="form-control") }}
<div class="input-group-append" onclick="togglePassword()">
<span class="input-group-text">
<i class="fas fa-eye"></i>
</span>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-md-auto mt-1 mb-2" align="center">
<button type="submit" class="btn btn-sm btn-primary btn-icon rounded-pill">
<span class="btn-inner--text">Sign in</span>
<span class="btn-inner--icon"><i class="fas fa-long-arrow-alt-right"></i></span>
</button>
</div>
<div class="col-md-auto text-center mt-2">
<p class="text-secondary-dark">or</p>
</div>
<div class="col-md-auto" align="center">
<button type="button" class="btn btn-md btn-secondary btn-icon-only">
<span class="btn-inner--icon">
<i class="fab fa-google"></i>
</span>
</button>
<button type="button" class="btn btn-md btn-secondary btn-icon-only">
<span class="btn-inner--icon">
<i class="fab fa-linkedin"></i>
</span>
</button>
</div>
</div>
</form>
I would like to display the errors that I validate using WTForms, but I am unsure of how to change the class of the original form element to is-invalid or is-valid, and how to create the labels for each error. I have looked into macros, but they don't seem to be able to modify the form element either.
Can someone point me in the right direction?

I faced the same problem and I wanted to avoid the use of any third package (flask-bootstrap), so I came up with this simple solution:
<div class="form-group">
{{ form.email.label }} <span class="text-danger">*</span>
{# her #}
{{ form.email(class="form-control" + (" is-invalid" if form.email.errors else "") + " rounded-0 shadow-none", **{"placeholder": "Your Email", "aria-describedby": "emailHelp", "autocomplete": "off"}) }}
<small id="emailHelp" class="form-text text-muted">We'll never share your data with anyone else.</small>
{% if form.email.errors %}
{% for error in form.email.errors %}
<div class="invalid-feedback">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
The trick is to use a simple ternary expression combined with string concatenation.

#cizario's answer is a great start. However, I found a better implementation for the errors. Using WTForm's Custom Widgets, you get the following widgets:
from wtforms.widgets import PasswordInput, CheckboxInput, TextInput
from wtforms.widgets.html5 import EmailInput
class BootstrapVerifyEmail(EmailInput):
"""Bootstrap Validator for email"""
def __init__(self, error_class=u"is-invalid"):
super(BootstrapVerifyEmail, self).__init__()
self.error_class = error_class
def __call__(self, field, **kwargs):
if field.errors:
c = kwargs.pop("class", "") or kwargs.pop("class_", "")
kwargs["class"] = u"%s %s" % (self.error_class, c)
return super(BootstrapVerifyEmail, self).__call__(field, **kwargs)
class BootstrapVerifyPassword(PasswordInput):
"""Bootstrap Validator for password"""
def __init__(self, error_class=u"is-invalid"):
super(BootstrapVerifyPassword, self).__init__()
self.error_class = error_class
def __call__(self, field, **kwargs):
if field.errors:
c = kwargs.pop("class", "") or kwargs.pop("class_", "")
kwargs["class"] = u"%s %s" % (self.error_class, c)
return super(BootstrapVerifyPassword, self).__call__(field, **kwargs)
class BootstrapVerifyBoolean(CheckboxInput):
"""Bootstrap Validator for boolean"""
def __init__(self, error_class=u"is-invalid"):
super(BootstrapVerifyBoolean, self).__init__()
self.error_class = error_class
def __call__(self, field, **kwargs):
if field.errors:
c = kwargs.pop("class", "") or kwargs.pop("class_", "")
kwargs["class"] = u"%s %s" % (self.error_class, c)
return super(BootstrapVerifyBoolean, self).__call__(field, **kwargs)
class BootstrapVerifyText(TextInput):
"""Bootstrap Validator for text"""
def __init__(self, error_class=u"is-invalid"):
super(BootstrapVerifyText, self).__init__()
self.error_class = error_class
def __call__(self, field, **kwargs):
if field.errors:
c = kwargs.pop("class", "") or kwargs.pop("class_", "")
kwargs["class"] = u"%s %s" % (self.error_class, c)
return super(BootstrapVerifyText, self).__call__(field, **kwargs)
This will add the invalid tag so that bootstrap can mark it as invalid. In the HTML, do something like this to add the error message:
{{ login_user_form.email.label(class_="form-control-label") }}
<div class="input-group input-group-merge">
<div class="input-group-prepend">
<span class="input-group-text" id="user"><i class="fas fa-user"></i></span>
</div>
{{ login_user_form.email(placeholder="name#example.com", class_="form-control", **{"aria-describedby": "inputGroupPrepend3", "required": ""}) }}
{% for error in login_user_form.email.errors %}
<div class="invalid-feedback">{{ error }}</div>
{% endfor %}
</div>

I ended up using a different library that provides Bootstrap widgets:
https://github.com/agdsn/wtforms-widgets
The usage gets really simple really fast:
from wtforms import validators
from wtforms.validators import Email
from wtforms_widgets.base_form import BaseForm
from wtforms_widgets.fields.core import StringField, PasswordField
class RegisterForm(BaseForm):
email = StringField('Email Address', [Email(), validators.DataRequired(message='Forgot your email address?')])
password = PasswordField('Password', [validators.DataRequired(message='Must provide a password. ;-)')])
<form method="POST" action="{{ url_for('auth.register') }}" accept-charset="UTF-8" role="form">
{% for field in form %}
{{ field(render_mode='horizontal', autocomplete='off') }}
{% endfor %}
<input type="submit" value="submit">
</form>
But this doesn't include validation.
To add validation, I put this in Jinja:
<form method="POST" action="{{ url_for('auth.register') }}" accept-charset="UTF-8" role="form">
{% for field in form %}
{{ field() }}
{% for error in field.errors %}
<div class="invalid-feedback">{{ error }}</div>
{% endfor %}
{% endfor %}
<input type="submit" value="submit">
</form>
And for flashed messages I use this in my base layout.html that I extend everywhere:
<div>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
{% if category == 'message' %}
<div class="alert alert-warning" role="alert">
{% else %}
<div class="alert alert-{{ category }}" role="alert">
{% endif %}
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>

When rendering your template using render_template, pass the 'is-valid' class to all the elements you want. Use this
class = '{{email_valid_class}} all other classes here'
Then in python return render_template('login.html',email_valid_class='is-valid')

Related

In Django website even if I add an item by pressing add to cart button the status of True for the product added is not shown and still false is visibl

I have made a django e-comm website , when I press the add to cart button on my home page I have written a function which should change the status of a product from false to true once it is added in the cart but it does not work
I have given my cart.py and index.html code
Thank you
cart.py
from django import template
register = template.Library()
#register.filter(name='is_in_cart')
def is_in_cart(product,cart):
keys = cart.keys()
for id in keys:
print(id , product.id)
if int(id) == product.id:
return True
return False
index.html
{% extends 'main/base.html'%}
{% block title %}
index
{% endblock %}
{% block content %}
{% load cart %}
<div class="container-fluid">
<div class="row">
<div class="col-lg-3">
<div class="list-group mt-3">
{% for items in category %}
{{items.Category_name}} <!--i first used a small c for category but it did not pick the words from my category.py fle-->
{% endfor %}
</div>
</div>
<div class="col-lg-9">
<div class="row">
{% for items in product %}
<div class="card mx-auto mb-3 mt-3" style="width: 18rem;">
<img class="card-img-top" src="{{items.product_image.url}}" alt="Card image cap">
<div class="card-body">
<h5 class="card-title">{{items.product_name}}</h5>
<p class="card-text"><b>{{items.product_price}}</b></p>
<p class="card-text"><b>{{items.product_detail}}</b></p>
{{product | is_in_cart:request.session.cart }}
<form action="/" mthod="POST">
{% csrf_token %}
<input hidden type="text" name='product' value='{{items.id}}'>
<input href="#" type="submit" class="float-right btn btn-light border btn-sm" value="Add to Cart">
</form>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endblock %}

Django models choice textField mapping

I am building a website for myself I am updating my projects from django admin I wanted to create a custom field which is a map of choice(tag color) and tag text.
I want to make it look something like this
This is my model
class Project(models.Model):
projectTemplate = CloudinaryField('image')
projectTitle = models.TextField(default="Title ")
desc = models.TextField(default="not mentioned")
projectLink = models.URLField(max_length=200, default="not mentioned")
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f'{self.projectTitle} - {self.desc[:15]} ..'
views.py
def projects(request):
Projects = Project.objects.all()
return render(request, 'devpage/projects.html',{'Projects': Projects})
This is my current DTL
{% for project in Projects %}
<div class="col">
<div class="card shadow-sm">
<img src="{{ project.projectTemplate.url }}" height="225" width="100%">
<div class="card-body">
<h6 class="text-center">
{{ project.projectTitle }}
</h6>
<p class="card-text">{{ project.desc }}</p>
<div class="mb-3">
<span class="badge rounded-pill bg-secondary">Django</span>
<span class="badge rounded-pill bg-success">Python</span>
</div>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-warning btn-outline-secondary"><a
href="{{ project.projectLink }}" target="_blank" type="button"
class="btn">View</a></button>
</div>
<small class="text-muted">{{ project.created_at }}</small>
</div>
</div>
</div>
</div>
{% endfor %}
I want a custom model field which takes choice of label color and corresponding text with dynamic size I tried using json(field name tags) but its not interactive its like {"primary":"django","success":"Python","seconday":"webApp"}
This is JSON DTL
//Inner for loop for tags
<div class="mb-3">
{% for color,text in Project.tags.items %}
<span class="badge rounded-pill bg-{{ color }}">{{ text }}</span>
{% endfor %}
</div>
Assuming you have a Tag model which is a ManyToManyField, you could just store the color as an attribute to the model.
Your model:
class Tag(models.Model):
text = models.CharField(max_length=32)
color = models.CharField(max_length=32, default="bg-success")
class Project(models.Model):
...
tags = models.ManyToManyField(Tag)
Your template:
{% for tag in Project.tags %}
<span class="badge rounded-pill {{ tag.color }}">{{ tag.text }}</span>
{% endfor %}
Some future improvement to this could be a subclass that pre-defines all of the colors.

Show image name inside the image field in Django forms

I have this edit or update form in which I want to display only the image name in the form for a better user experience so that the user could know which image he has uploaded while creating the data.
I am storing the image name in the model as well,but i want to display the image name inside the image field.
forms.py
class MenuCategoryForm(forms.ModelForm):
image = forms.ImageField(allow_empty_file=True, required=False)
class Meta:
model = MenuCategory
fields = ['name', 'description', 'menu_options']
view
def menu_category_update(request, id):
item = MenuCategory.objects.get(id=id)
if request.method == 'POST':
form = MenuCategoryForm(request.POST, request.FILES, instance=item)
if form.is_valid():
if request.FILES['image']:
image = request.FILES['image']
image_url = upload_image(image, 'menu_category', image.name)
obj = form.save(commit=False)
obj.image_url = image_url
form.save()
else:
form.save()
return redirect('menu-category')
else:
form = MenuCategoryForm(instance=item)
context = {
'form': form,
'item': item
}
return render(request, 'menu/menu_category_update.html', context)
Template
{% extends 'partials/base.html' %} {% load crispy_forms_filters %} {% load
crispy_forms_tags %}
<!-- {% block title %}Menu category {% endblock %} -->
{% block content %}
<div class="container edit-form-flex">
<div class="row Edit-form-box">
<div class="form-inner-box bg-white">
<div class="heading-editing">
<h3>Edit menu category</h3>
</div>
<form method="POST" class="add-new-form edit-form" enctype="multipart/form-data">
{% csrf_token %} {{ form|crispy }}
<div class="update-buttons-container">
<button class="btn btn-info1" type="submit" value="Update">
Update
</button>
<a class="btn btn-secondary" href="{% url 'menu-category' %}"
>Cancel</a
>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
Well in other to get the name of the image in your form you will be better off in using a form initial like so:
def menu_category_update(request,id):
item = MenuCategory.objects.get(id=id)
if request.method == 'POST':
form = MenuCategoryForm(request.POST, request.FILES, instance=item)
if form.is_valid:
obj = form.save(commit=False)
# obj.username = request.user
form.save()
return redirect('menu-category')
else:
form = MenuCategoryForm(
initial={'image':item.image,
'name':item.name,
'description':iteem.description,
})# this are the fields that we want to show in the form
context = {
'form': form,
'item': item
}
return render(request, 'menu/menu_category_update.html', context)
In your form html you will apply the form initial to the field that you want to show in your form, by doing something like this: form.initial.name As i have illustrated below :
{% extends 'partials/base.html' %} {% load crispy_forms_filters %} {% load
crispy_forms_tags %}
<!-- {% block title %}Menu category {% endblock %} -->
{% block content %}
<div class="container edit-form-flex">
<div class="row Edit-form-box">
<div class="form-inner-box bg-white">
<div class="heading-editing">
<h3>Edit menu category</h3>
</div>
<form method="POST" class="add-new-form edit-form" enctype="multipart/form-data">
{% csrf_token %}
<label> Image name </label>
{{form.initial.name}}
<label> Description </label>
{{ form.initial.description }}
<label> Image </label>
{{ form.initial.image }}
<div class="update-buttons-container">
<button class="btn btn-info1" type="submit" value="Update">
Update
</button>
<a class="btn btn-secondary" href="{% url 'menu-category' %}"
>Cancel</a
>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

django not displaying error_messages in forms

i am new to django and i have this form i am working on i have tried everything possible i cant seem to display the form error messages. i have tried using a def clean method nothing happens when i try to submit an empty form..i have tired switing to using FormView i have also tried using function view i have tried using a for loop and adding {{ form.non_field_errors }} in my template nothing pops up.
my app/forms.py
from django import forms
class ContactForm(forms.Form):
name = forms.CharField( label='Your Name', min_length=2, max_length=25, required=True, error_messages ={'required':'Please tell Oluwafemi your name'})
email = forms.EmailField(label='Your Email', required=True, error_messages={'invalid':'Please fill in a valid email'})
subject = forms.CharField(label='Subject', min_length=4, max_length=100, required=True)
message = forms.CharField(widget=forms.Textarea(attrs={'placeholder':'Write Oluwafemi a Message'}), error_messages ={'required':'Please write something for Oluwafemi'})
my app/views.py
from django.views.generic import TemplateView
from django.shortcuts import render
from django.core.mail import send_mail
from .forms import ContactForm
from django.http import HttpResponseRedirect
# Create your views here.
class ProfilePageView(TemplateView):
template_name = 'femi_profile.html'
form_class = ContactForm
success_url = 'femiir'
def get(self, request, *args, **kwargs):
form = self.form_class()
return render(request, self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
name = form.cleaned_data['name']
email = form.cleaned_data['email']
subject = form.cleaned_data['subject']
message = form.cleaned_data['message']
# Send the email
send_mail(
f"{subject} - message from {name} ", # message subject
message, # Message Body
email, # Sender's mail (From mail)
['d4rahl#gmail.com'], # Reciever's mail(To mail)
)
return HttpResponseRedirect('femiir')
return render(request, self.template_name, {'form': form})
snippet of my html template
<div class="col-lg-7 mt-5 mt-lg-0 d-flex align-items-stretch">
<form method="post">
{% csrf_token %}
<div class="form-row">
<div class="form-group col-md-6">
{{ form.name|as_crispy_field }}
</div>
<div class="form-group col-md-6">
{{ form.email|as_crispy_field }}
</div>
</div>
<div class="form-group">
{{ form.subject|as_crispy_field }}
{{ form.message|as_crispy_field }}
</div>
<div class="text-center"><button type="submit" class="btn btn-primary ">Send Message</button></div>
</form>
</div>
please what i my doing wrong
Did you try looping through form.errors in template?
{% if form.errors %}
{% for field in form %}
{% for error in field.errors %}
{{ error }}
{% endfor %}
{% endfor %}
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
{% endif %}
When you are rendering fields manually you would have to use error tags.
{{ form.name_of_field.errors }}
<form method="post">
{% csrf_token %}
<div class="form-row">
<div class="form-group col-md-6">
{{ form.name|as_crispy_field }}
{{ form.name.errors}}
</div>
<div class="form-group col-md-6">
{{ form.email|as_crispy_field }}
{{ form.email.errors}}
</div>
</div>
<div class="form-group">
{{ form.subject|as_crispy_field }}
{{ form.subject.errors}}
{{ form.message|as_crispy_field }}
{{ form.message.errors}}
</div>
<div class="text-center"><button type="submit" class="btn btn-primary ">Send Message</button></div>
</form>
https://docs.djangoproject.com/en/dev/topics/forms/#rendering-fields-manually

How to show filtered items only after adding to cart on home page in django?

i am practicing django by making an ecommerce app. I also share the video so that you can also check the problem.
https://youtu.be/crYlZ7Bo8y4
Application is working perfectly but when i filter the product according to selected category and press add to cart button then it will show all products of all categories instead of showing selected products under that category.
Can you please help me out in this.
index.html page:
{% extends 'base.html' %}
{% block content %}
{% load cart %}
{% load custom_filter %}
<!-- body -->
<div class="container-fluid mt-3">
<div class="row">
<!-- filter -->
<div class="col-lg-3 mx-auto">
<div class="list-group">
All Products
{% for category in categories %}
<a href="/?category={{category.id}}"
class="list-group-item list-group-item-action">{{category.name}}</a>
{% endfor %}
</div>
</div>
<!-- all products -->
<div id='products' class="col-lg-9 mx-auto">
<div class="row mx-auto">
{% for product in products %}
<div class="card mx-auto mb-3" id={{product.id}} style="width: 18rem;">
<img class="card-img-top" src="{{product.image.url}}" alt="Card image cap">
<div class="card-body">
<p class="card-title">{{product.name}}</p>
<p class="card-text"><b>{{product.price|currency}}</b></p>
<!-- {{product | is_in_cart:request.session.cart }} -->
</div>
<div class="card-footer p-0 no-gutters">
{% if product|is_in_cart:request.session.cart %}
<div class="row no-gutters">
<form action="/#{{product.id}}" class="col-2 " method="post">
{% csrf_token %}
<input hidden type="text" name='product' value='{{product.id}}'>
<input hidden type="text" name='remove' value='True'>
<input type="submit" value=" - " class="btn btn-block btn-light border-right">
</form>
<div class="text-center col">{{product|cart_quantity:request.session.cart}} in Cart</div>
<form action="/#{{product.id}}" class="col-2 " method="post">
{% csrf_token %}
<input hidden type="text" name='product' value='{{product.id}}'>
<input type="submit" value=" + " class="btn btn-block btn-light border-left">
</form>
</div>
{% else %}
<form action="/#{{product.id}}" method="POST" class="btn-block">
{% csrf_token %}
<input hidden type="text" name='product' value='{{product.id}}'>
<input type="submit" class="float-right btn btn-light form-control"
value="Add To Cart">
</form>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endblock %}
index.py:
from django.shortcuts import render , redirect , HttpResponseRedirect
from user.models.product import Product
from user.models.category import Category
from django.views import View
# Create your views here.
class Index(View):
def post(self , request):
product = request.POST.get('product')
remove = request.POST.get('remove')
cart = request.session.get('cart')
if cart:
quantity = cart.get(product)
if quantity:
if remove:
if quantity<=1:
cart.pop(product)
else:
cart[product] = quantity-1
else:
cart[product] = quantity+1
else:
cart[product] = 1
else:
cart = {}
cart[product] = 1
request.session['cart'] = cart
print('cart', request.session['cart'])
return redirect('user:homepage')
def get(self , request):
return HttpResponseRedirect(f'/store{request.get_full_path()[1:]}')
def store(request):
cart = request.session.get('cart')
if not cart:
request.session['cart'] = {}
products = None
categories = Category.get_all_categories()
categoryID = request.GET.get('category')
if categoryID:
products = Product.get_all_products_by_categoryid(categoryID)
else:
products = Product.get_all_products();
data = {}
data['products'] = products
data['categories'] = categories
print('you are : ', request.session.get('email'))
return render(request, 'index.html', data)
urls.py:
from django.urls import path
from .views.index import Index,store
from .views.signup import Signup
from .views.login import Login,logout
app_name = 'user'
urlpatterns = [
path('', Index.as_view(), name='homepage'),
path('store', store, name='store'),
path('signup', Signup.as_view(), name='signup'),
path('login', Login.as_view(), name='login'),
path('logout', logout, name='logout'),
]
cart.py template tag:
from django import template
register = template.Library()
#register.filter(name='is_in_cart')
def is_in_cart(product , cart):
keys = cart.keys()
for id in keys:
if int(id) == product.id:
return True
return False;
#register.filter(name='cart_quantity')
def cart_quantity(product , cart):
keys = cart.keys()
for id in keys:
if int(id) == product.id:
return cart.get(id)
return 0;
#register.filter(name='price_total')
def price_total(product , cart):
return product.price * cart_quantity(product , cart)
#register.filter(name='total_cart_price')
def total_cart_price(products , cart):
sum = 0 ;
for p in products:
sum += price_total(p , cart)
return sum
Custom_filter.py template tag:
from django import template
register = template.Library()
#register.filter(name='currency')
def currency(number):
return "₹ "+str(number)
#register.filter(name='multiply')
def multiply(number , number1):
return number * number1
referer = request.META['HTTP_REFERER']
return redirect(referer)
You can get the referring page of request instead of redirecting a constant link.You can also check META

Resources