I am using Django and Bootstrap 4. I want to customize the way form validation information is displayed in a template I am using. When an error occurs in the current template, the invalid input element has a red outline and the error message is displayed as a pop-up. Also, the input elements that have been inputted correctly don't have a green outline, they just remain without an outline.
I'd like to do the following:
display the error message underneath the field when an error occurs
give the green outline to the fields whose input is correct
I tried multiple solutions, including:
this tutorial
this GitHub gist
the Stack Overflow answers from this question
None worked from my case.
My current code:
submit_job_listing.html (the template):
{% extends "base.html" %}
{% block title %} Submit a job {% endblock %}
{% block nav_item_post_a_job %}active{% endblock nav_item_post_a_job %}
{% block head %}
{{ job_listing_form.media.css }}
{% endblock %}
{% block content %}
<h1>Submit a job listing</h1>
<div class="container">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="row text-center">
<h2> <b>Job listing information</b> </h2>
</div>
<br/>
{% for field in job_listing_form.visible_fields %}
<div class="form-group">
<div class="row">
<p>{{ field.label_tag }}</p>
</div>
<div class="row">
{% if field.help_text %}
<p class="form-text text-muted">{{ field.help_text }}</p>
{% endif %}
</div>
<div class="row">
{{ field }}
</div>
</div>
{% endfor %}
<br/>
<div class="row text-center">
<h2> <b>Employer information</b> </h2>
</div>
<br/>
{% for field in employer_form.visible_fields %}
<div class="form-group">
<div class="row">
<p>{{ field.label_tag }}</p>
</div>
<div class="row">
{% if field.help_text %}
<p class="form-text text-muted">{{ field.help_text }}</p>
{% endif %}
</div>
<div class="row">
{{ field }}
</div>
</div>
{% endfor %}
<input type="submit" value="Submit" class="btn btn-primary" />
</form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
{{ job_listing_form.media.js }}
</div>
{% endblock %}
views.py (relevant parts):
def submitJobListing(request):
if request.method == "POST":
employer_form = EmployerForm(request.POST, request.FILES)
job_listing_form = JobListingForm(request.POST, request.FILES)
#check if employer with that name already exists
employer_name = str(request.POST.get("name", ""))
try:
employer_with_the_same_name = Employer.objects.get(name=employer_name)
except ObjectDoesNotExist:
employer_with_the_same_name = None
employer = None
if (employer_with_the_same_name != None):
employer = employer_with_the_same_name
if employer == None and employer_form.is_valid() and job_listing_form.is_valid():
employer = employer_form.save()
job_listing = job_listing_form.save(commit=False)
job_listing.employer = employer
job_listing.save()
job_listing_form.save_m2m()
return SimpleTemplateResponse("employers/successful_job_listing_submission.html")
else:
employer_form = EmployerForm()
job_listing_form = JobListingForm()
context = {
"employer_form": employer_form,
"job_listing_form": job_listing_form,
}
return render(request, 'employers/submit_job_listing.html', context)
forms.py (relevant parts):
class EmployerForm(forms.ModelForm):
name = forms.CharField(max_length=100)
#location = forms.CharField(widget=LocationWidget)
short_bio = forms.CharField(widget=forms.Textarea)
website = forms.URLField()
profile_picture = forms.ImageField()
def __init__(self, *args, **kwargs):
super(EmployerForm, self).__init__(*args, **kwargs)
for visible in self.visible_fields():
if isinstance(visible.field.widget, (forms.widgets.TextInput, forms.widgets.Textarea, forms.widgets.URLInput, LocationWidget)):
visible.field.widget.attrs['class'] = 'form-control'
if isinstance(visible.field.widget, forms.widgets.ClearableFileInput):
visible.field.widget.attrs['class'] = 'form-control-file'
class Meta:
model = Employer
fields = ["name", "short_bio", "website", "profile_picture"]
class JobListingForm(forms.ModelForm):
job_title = forms.CharField(max_length=100, help_text="What's the job title?")
job_description = forms.CharField(widget=forms.Textarea)
job_requirements = forms.CharField(widget=forms.Textarea)
what_we_offer = forms.CharField(widget=forms.Textarea)
location = forms.CharField(widget=LocationWidget)
remote = forms.BooleanField()
job_application_url = forms.URLField()
point_of_contact = forms.EmailField()
categories = forms.ModelMultipleChoiceField(
queryset=Category.objects.all(),
widget=forms.CheckboxSelectMultiple,
required=True)
def __init__(self, *args, **kwargs):
super(JobListingForm, self).__init__(*args, **kwargs)
for visible in self.visible_fields():
if isinstance(visible.field.widget, (forms.widgets.TextInput, forms.widgets.Textarea, forms.widgets.URLInput, forms.widgets.EmailInput, LocationWidget)):
visible.field.widget.attrs['class'] = 'form-control'
if isinstance(visible.field.widget, (forms.widgets.CheckboxInput, forms.widgets.CheckboxSelectMultiple)):
visible.field.widget.attrs['class'] = 'form-check'
class Meta:
model = JobListing
fields = ["job_title", "job_description", "job_requirements", "what_we_offer", "location", "remote", "job_application_url", "categories", "point_of_contact"]
Can someone tell me how can I achieve the effects that I want?
Update 1:
I tried adding the code that checks for field.errors, but it doesn't work.
This is the submit_job_listing.html template file:
{% extends "base.html" %}
{% load widget_tweaks %}
{% block title %} Submit a job {% endblock %}
{% comment %} https://stackoverflow.com/questions/50028673/changing-active-class-in-bootstrap-navbar-not-staying-in-inherited-django-templa {% endcomment %}
{% block nav_item_post_a_job %}active{% endblock nav_item_post_a_job %}
{% block head %}
{{ job_listing_form.media.css }}
<!---
<style>
input, select {width: 100%}
</style>
--->
{% endblock %}
{% block content %}
{% comment %}
https://simpleisbetterthancomplex.com/article/2017/08/19/how-to-render-django-form-manually.html
{% endcomment %}
<h1>Submit a job listing</h1>
<div class="container">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="row text-center">
<h2> <b>Job listing information</b> </h2>
</div>
<br />
<div class="row text-center">
<p> <b>Note: Fields marked with an asterisk (*) are required</b> </p>
</div>
<br/>
{% for field in job_listing_form.visible_fields %}
<div class="form-group">
<div class="row">
<p>{{ field.label_tag }}{{ field.field.required|yesno:"*," }}</p>
</div>
<div class="row">
{% if field.help_text %}
<p class="form-text text-muted">{{ field.help_text }}</p>
{% endif %}
</div>
<div class="row">
{{ field }}
<p> {{ field.errors }} </p>
{% if field.errors %}
<div class="border-bottom-danger">
{{ field.errors }}
</div>
{% endif %}
</div>
</div>
{% endfor %}
<br/>
<div class="row text-center">
<h2> <b>Employer information</b> </h2>
</div>
<br />
<div class="row text-center">
<p> <b>Note: Fields marked with an asterisk (*) are required</b> </p>
</div>
<br/>
{% for field in employer_form.visible_fields %}
<div class="form-group">
<div class="row">
<p>{{ field.label_tag }}{{ field.field.required|yesno:"*," }}</p>
</div>
<div class="row">
{% if field.help_text %}
<p class="form-text text-muted">{{ field.help_text }}</p>
{% endif %}
</div>
<div class="row">
{{ field }}
</div>
</div>
{% endfor %}
<input type="submit" value="Submit" class="btn btn-primary" />
</form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
{{ job_listing_form.media.js }}
</div>
{% endblock %}
When I just click the submit button without inputting anything, all the inputs have a red outline, but nothing gets printed out as field.errors. I don't know why is that. Why is that?
Did you tried debugging in the template ?
I did something like you want to achieve :
<form method="post"> {% csrf_token %}
{% for field in form %}
<div class="form-group">
{{ field }}
<label class="control-label" for="{{ field.id_for_label }}">{{ field.label }}</label>
{% if field.errors %}
<div class="border-bottom-danger">
{{ field.errors }}
</div>
{% endif %}
</div>
{% endfor %}
<button class="btn btn-block" type="submit">Submit</button>
</form>
Where :
{{ field }} is the input, where you are correctly adding visible.field.widget.attrs['class'] = 'form-control'
Then, errors you want (I guess) are stored in the field, so you could get its with {{ field.errors }} if it has some.
The difference between your code and mine is that I'm using field.errors instead if field.help_text, I hope it can help.
I suggest you to put breakpoint in your template, then analyze your form object. Then you can do whatever you want in your frontend.
If no error : Add class outline-green to your input wrapper (the <div class="row")
If error : Display the error within a div underneath your input
PS : If you want to outline the input, and not the row wrapper, you can do it through css (or better with Sass if you are using it):
.row.outline-green > input{
border: 1px solid green;
}
<div class="row outline-green">
{{ field }}
</div>
{% load widget_tweaks %}
<div class="col-12 col-md-6">
<div class="form-group">
<label for="{{ form.field1.id_for_label }}" class="text-capitalize">{{ form.field1.label|title }}{% if form.field1.field.required %}<span class="text-danger ml-1">*</span>{% endif %}</label>
{% if form.is_bound %}
{% if form.field1.errors %}
{% render_field form.name class="form-control is-invalid" %}
{% for error in form.field1.errors %}
<div class="invalid-feedback">
{{ error }}
</div>
{% endfor %}
{% else %}
{% render_field form.field1 class="form-control is-valid" %}
{% endif %}
{% else %}
{% render_field form.field1 class="form-control" %}
{% endif %}
{% if form.field1.help_text %}
<small class="form-text text-muted">{{ form.field1.help_text }}</small>
{% endif %}
</div>
</div>
</div>
I'm having some problem with my theme for my e-commerce store. Currently, the product images are aligned in a carousel form. (One large main image, 3-5 thumbnails images below in carousel form)
I'd like to change this to show all photos in same sizes but vertically (top to bottom)
This is my code, how do I change it? I've read stuff online and tried making changes but nothing is happening. Please help.
{{ set(this, 'title', product.seo_title) }}
{% set mainStyles = load.get( loadQuery.cms_items("main-styles", {limit: 50}) ) %}
{% set buttonText = mainStyles[0].settings["Add to cart button text"]|default('Add to Cart') %}
{% set buttonColor = mainStyles[0].settings["Add to cart button color"]|default('inherit') %}
{% set maxRelatedProducts = mainStyles[0].settings["Number of rows for related items"]|default(1) * 3 %}
{% set quanSelector = mainStyles[0].settings["Quantity selector on product page"] %}
{% set imageCrop = mainStyles[0].settings["Product thumbnail aspect ratio"]|default('Smart crop') %}
{% if imageCrop == 'Wide' %}
{% set imageCrop = 'wide' %}
{% elseif imageCrop == 'Tall' %}
{% set imageCrop = 'tall' %}
{% endif %}
<div class="page product" data-productid="{{ product.id }}">
<div class="container">
<div class="col-sm-7">
<div class="fotorama"
data-width="100%"
data-arrows="false"
data-loop="true"
data-autoplay="false"
data-thumbwidth="113"
data-thumbheight="113"
data-thumbborderwidth="4"
data-thumbmargin="5">
{% if product.mainImage %}
<a href="{{ product.mainImage.getImageSize("jumbo") }}">
<img src="{{ product.mainImage.getImageSize("jumbo") }}">
</a>
{% endif %}
</div>
<div class="product-no-image{% if product.mainImage %} hidden{% endif %}">
<img src="/img/icons/image.svg">
</div>
<div class="hidden-xs">
<hr>
{% set relatedProducts = campaign.recommendations({ "product_id": product.id, "limit": maxRelatedProducts }) %}
{% if relatedProducts|length %}
<div class="row related items-grid">
<div class="col-sm-12">
<div class="block-title">
<h3>Related products</h3>
</div>
<div class="items">
{% for product in relatedProducts %}
<div class="col-sm-4 col-xs-6 item {{ imageCrop }}">
<a href="{{ site_path(product.url) }}">
{% if product.mainImage %}
<div class="image"
style="background-image: url('{{ product.mainImage.getImageSize("medium") }}');"></div>
{% else %}
<div class="image empty"></div>
{% endif %}
<p class="title">{{ product.title }}</p>
<p class="price">
{{ product.defaultPrice | currency }}
</p>
</a>
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
</div>
</div>
<div class="col-sm-5">
<div class="product-head">
<h2>{{ product.title }}</h2>
<p class="price">
<span id="price">{{ product.price | currency }}</span>
<span class="compare">
<span id="compare-price">{{ product.compare_price | currency(NULL, {"force_prefix_symbol": true}) }}</span>
</span>
</p>
{% if product.variants %}
<div class="variants">
{% for variant in product.allVariants %}
<div class="item">
<span class="label-heading">{{ variant.variant }}</span>
{% if variant.selectorType == "dropdown" %}
<div class="select2">
<select data-variant="{{ loop.index0 }}"
class="product-{{ variant.variant|preg_replace('/[^a-zA-Z0-9]/')|lower }}-select product-select"
title="">
</select>
</div>
{% else %}
<div class="product-colors product-{{ variant.variant|preg_replace('/[^a-zA-Z0-9]/')|lower }}-round">
{% for option in variant.variantOptions %}
<button type="button"
data-variant="{{ loop.parent.loop.index0 }}"
data-option="{{ option.option|preg_replace('/[^a-zA-Z0-9]/')|lower }}"
class="btn-rounded product-color"
style="{% if option.thumbnail.image %}background-image: url('{{ option.thumbnail.imageUrl }}');{% else %}background-color: {{ option.thumbnail.color }}{% endif %}"></button>
{% endfor %}
</div>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
{% if quanSelector %}
<div id="quantity-selector" class="quantity">
<span class="label-heading">Quantity</span>
<input type="number">
<button class="down"></button>
<button class="up"></button>
</div>
{% endif %}
{{ system.getApp('ProductCustomizer', {'product_id': product.id} ) }}
<button class="btn" id="buy-it-now"
style="background-color: {{ buttonColor }}">{{ buttonText }}</button>
{{ system.getApp('SecurityBadge', {} ) }}
{{ system.getApp('QuantityLeft', {'product_id': product.id} ) }}
{{ system.getApp('Timer', {'product_id': product.id}) }}
</div>
{% if product.textareaModels %}
<div class="panel-group descriptions" id="accordion" role="tablist" aria-multiselectable="true">
{% for textarea in product.textareaModels %}
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="section-heading{{ loop.index }}">
<h4 class="panel-title">
<a role="button" data-toggle="collapse" data-parent="#accordion"
href="#section{{ loop.index }}"
aria-expanded="{% if loop.index == 1 %}true{% else %}false{% endif %}"
aria-controls="section{{ loop.index }}">
{{ textarea.name|raw }}
<span class="icon__state"></span>
</a>
</h4>
</div>
<div id="section{{ loop.index }}"
class="panel-collapse collapse{% if loop.index == 1 %} in{% endif %}" role="tabpanel"
aria-labelledby="section-heading{{ loop.index }}">
<div class="panel-body">
<div class="typography">{{ textarea.text | raw }}</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% endif %}
</div>
<div class="visible-xs">
{% set relatedProducts = campaign.recommendations({ "product_id": product.id, "limit": maxRelatedProducts }) %}
{% if relatedProducts|length %}
<div class="row related items-grid">
<div class="col-sm-12">
<div class="block-title">
<h3>Related products</h3>
</div>
<div class="items">
{% for product in relatedProducts %}
<div class="col-sm-4 col-xs-6 item {{ imageCrop }}">
<a href="{{ site_path(product.url) }}">
{% if product.mainImage %}
<div class="image"
style="background-image: url('{{ product.mainImage.getImageSize("medium") }}');"></div>
{% else %}
<div class="image empty"></div>
{% endif %}
<p class="title">{{ product.title }}</p>
<p class="price">
{{ product.defaultPrice | currency }}
</p>
</a>
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
</div>
<div class="row">
<div class="col-sm-12">
{{ system.getApp('Reviews', {'product_id': product.id} ) }}
</div>
</div>
</div>
</div>
I'm looking for a way to add a class, which I could modify in css, to images and videos in both editor forms and html templates displaying the form. I'm using the SummernoteInplaceWidget() from django-summernote package and it's working fine except this feature.
I use bootstrap 4 with it.
Any ideas?
Thanks in advance.
forms.py:
from django.forms import ModelForm
from django_summernote.widgets import SummernoteInplaceWidget
from .models import Post
class PostFormModel(ModelForm):
class Meta:
model = Post
fields = ['title', 'content', 'image', ]
widgets = {
'content': SummernoteInplaceWidget(attrs={'class': 'summer-img-class'}),
}
post_form.html:
{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block mt %} mt-2 {% endblock mt %}
{% block content %}
<div class="content-section">
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Blog Post</legend>
{{ form|crispy }}
{% if form.image.errors %}
<div class="alert alert-danger">
{{ form.image.errors }}
</div>
{% endif %}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Post</button>
</div>
</form>
{% block jquery %}
{{ form.media }}
{% endblock %}
</div>
{% endblock content %}
post_detail.html:
{% extends "blog/base.html" %}
{% block mt %} mt-2 {% endblock mt %}
{% block content %}
<article class="media content-section">
<div class="shadow-sm rounded row media-body">
<div class="col-md-3 col-sm-3 col-3 align-self-center">
<img class="rounded-circle article-img align-middle mx-auto d-none d-sm-block " src="{{ object.author.profile.image.url }}">
<img class="rounded-circle article-img-sm align-midle mx-auto d-block d-sm-none " src="{{ object.author.profile.image.url }}"">
</div>
<div class="col-md-9 col-sm-9 col-9">
<div class="article-metadata">
<a class="mr-2" href="{% url 'user-posts' post.author.username %}">{{ post.author }}</a>
<small class="text-muted">{{ object.date_posted|date:"F d, Y" }}</small>
{% if object.author == user %}
<div>
<a class="btn btn-secondary btn-sm mt-1 mb-1" href="{% url 'post-update' object.id %}">Update</a>
<a class="btn btn-danger btn-sm mt-1 mb-1" href="{% url 'post-delete' object.id %}">Delete</a>
</div>
{% endif %}
</div>
<h2><a class="article-title" href="{% url 'post-detail' post.id %}">{{ object.title }}</a></h2>
</div>
<div class="row media-body pt-3">
<div class="col-md-12">
{% if post.image %}
<img alt="" src="{{ object.image.large.url }}" class="post_image rounded mx-auto mb-2 d-block"/>
{% endif %}
<p class="article-content ml-3">{{ object.content|safe }}</p>
</div>
</div>
</div>
</article>
{% endblock content %}
I tried to add some styling to .article-content, it didn't have any effect, the same for this class in css (I imagine it can be more effectively):
.summer-img-class * {
max-width: 100%;
max-height: 400px;
object-fit: cover;
margin: auto;
}
Maybe it should be done with Jquery somehow, I don't know.
I have a variable extracted in my Twig file which is the result of a selected :
<div class="col-sm-7">
<div class="product-information"><!--/product-information-->
<img src="images/product-details/new.jpg" class="newarrival" alt="" />
<h2>{{ produit.nom }}</h2>
<p>{{ produit.categorie.nomcat }}</p>
<p>{{ produit.description }}</p>
<span>
<span>€ {{ produit.prix}}</span>
</span>
<p>
<script>
function myFunction() {
var x = document.getElementById("co").value;
document.getElementById("demo").innerHTML = x;
}
</script>
<b>Couleur :</b>
<select id="co" onchange="myFunction()">
{% for coul in produit.couleur %}
<option value="{{ coul.nomc }}">{{ coul.nomc }}</option>
{% endfor %}
</select>
</p>
<p><b>Marque :</b> {{ produit.marque}}</p>
<p>
<a href="{{ path('ajouter', { 'id' : produit.id }) }}">
<button type="button" class="btn btn-fefault cart" >
<i class="fa fa-shopping-cart"></i>
Ajouter au Panier
</button>
</a>
</p>
</div><!--/product-information-->
i try to extract the selected item from the select by put it in a variable,
somthing like that : {% set var %} <p id=demo> </p>
when i put the variable {{ var }} i get the selected item in my screen
but when i put it in the path i get the problem :
the path is somthing like that : ../../../id/ /
he put the balise p not the value of my variable
I have tried : <a href="{{ path('ajouter', { 'id' : produit.id, 'coul' : {{ var }} }) }}">
you can use FOSJsRoutingBundle to generate routes on client side (right in JS)
I've been following Symfony2 documentation on how to override a Twig form template which is working however there is one problem. The names of the fields are not lining up next to the form boxes.
I should be getting this:
But instead my Name and Email labels are not aligning. I get this:
Can someone show me what I'm doing wrong? (I've copied over the code from the template and wrapped the form code with it.)
Overridden template code
{% block form_widget_simple %}
<div class="row collapse">
<div class="large-2 columns">
<label class="inline"></label>
</div>
<div class="large-10 columns">
{% spaceless %}
{% set type = type|default('text') %}
<input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}/>
{% endspaceless %}
</div>
</div>
{% endblock form_widget_simple %}
{% block email_widget %}
<div class="row collapse">
<div class="large-2 columns">
<label class="inline"></label>
</div>
<div class="large-10 columns">
{% spaceless %}
{% set type = type|default('text') %}
<input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}/>
{% endspaceless %}
</div>
</div>
{% endblock email_widget %}
{% block textarea_widget %}
{% spaceless %}
<textarea rows="12" {{ block('widget_attributes') }}>{{ value }}</textarea>
{% endspaceless %}
{% endblock textarea_widget %}
Original Zurb Foundation template code http://foundation.zurb.com/templates/contact.html
<form>
<div class="row collapse">
<div class="large-2 columns">
<label class="inline">Your Name</label>
</div>
<div class="large-10 columns">
<input type="text" id="yourName" placeholder="Jane Smith">
</div>
</div>
<div class="row collapse">
<div class="large-2 columns">
<label class="inline"> Your Email</label>
</div>
<div class="large-10 columns">
<input type="text" id="yourEmail" placeholder="jane#smithco.com">
</div>
</div>
<label>What's up?</label>
<textarea rows="4"></textarea>
<button type="submit" class="radius button">Submit</button>
</form>
Thanks to Michael Sivolobov for his guidance. Hope this helps others with the same question. Overwrote form_row and wrapped {{ form_label(form) }} in the <div class>.
% block form_row %}
{% spaceless %}
<div>
<div class="large-2 columns">
<label class="inline">{{ form_label(form) }}</label>
</div>
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endspaceless %}
{% endblock form_row %}