'trouble showing desired checkbox validation state w/ bootstrap5 for django model form w/ m2m field and checkboxselectmultiple widget

I have a checkboxselectmultiple on an m2m model field in an ModelForm that is required - meaning at least one of the choices must be selected. I am using the boostrap5 was-validated class on my form:

<form  method="POST" action="{{ request.path }}" {% if attempt_submit %}class="was-validated"{% endif %}>

This question is about how the validation shows up on my form with bootstrap5. Should be red border and red ! if not validated, green border and checkmark if so. However, for my checkboxes, if I don't have any selected (and everything else on the form validates), the form will show each checkbox option as green instead of red. Yet, it does know that it's invalid because the page focus will come back up the checkbox area to show the user what to correct (and it doesn't pass form.is_valid() in views.py.

Why are these labels and boxes still showing green and how can I show them as red until I select one and it's now valid?

Along the lines of this post, I have tried adding

{% if form.sales_location.field.required %}required{% else %}form.sales_location.field.required=""{% endif %}

to the checkbox <input>, but then each field is required and if I select one, the other remaining options still remain red - as if every option would have to be selected for the form to validate. Am I supposed to do this anyway and then add something else (JS?) to disable that?

Not sure exactly what code would be helpful to see...

in models.py, this is the field:

sales_location = models.ManyToManyField(SalesLocation, verbose_name="Where do you sell your products? (select all that apply)" )

in forms.py

        model = AssessmentProfile
        fields = [
            'sales_location', 
            ...
            ]
        widgets = {
            'sales_location': forms.CheckboxSelectMultiple(attrs={
                'class': 'form-check',}),
                    }

I add this because I read this post about making sure that I use a `ModelMultipleChoiceField' - but I assume that is already happening because it's a model form.(?)

Probably most important, in the template thisform.html, here's how I'm manually adding this form element:

<div class="field-wrapper">
        {{ form.sales_location.label_tag }}
        <ul id="id_sales_location" class="form-check">
        {% for pk, choice in form.sales_location.field.widget.choices %} 
            <li>
                <input {% for location in location_qs %}{% if location == pk %}checked='checked'{% endif %}{% endfor %} 
                    name="sales_location" class="form-check-input" type="checkbox" value="{{ pk }}" id="id_sales_location_{{forloop.counter0}}"
                    {% if already_submitted %}disabled="disabled"{% endif %}>
                <label class="form-check-label" for="id_sales_location_{{forloop.counter0}}">
                {{ choice }}
                </label>
            </li>
        {% endfor %}
        </ul>
        </div>

Also, I tried updating css to manually format red, but think that doesn't address the root of the problem, plus, I wasn't able to do it successfully anyway.

Thanks for taking a look and for any suggestions.



Solution 1:[1]

In the end, I used javascript to solve this problem.

I updated the form template

  <div class="field-wrapper">
        {{ form.sales_location.label_tag }}
        <ul id="id_sales_location" class="form-check">
        {% for pk, choice in form.sales_location.field.widget.choices %} 
            <li>
                <input {% for location in location_qs %}{% if location == pk %}checked='checked'{% endif %}{% endfor %} 
                    name="sales_location" class="form-check-input" type="checkbox" value="{{ pk }}" id="id_sales_location_{{forloop.counter0}}"
                    {% if not form.sales_location.field.required %} {% else %} required {% endif %}
                    {% if already_submitted %}disabled="disabled"{% endif %}>
                <label class="form-check-label" for="id_sales_location_{{forloop.counter0}}">
                {{ choice }}
                </label>
            </li>
        {% endfor %}
        </ul>
        </div>

to add required to the input if the checkbox is required. This allows all the checkboxes to come up red when validating, if the field is empty.

Then, I added this javascript to remove 'required' if it's checked.

<script> 
    // Select all checkboxes using querySelectorAll.
    var checkboxes = document.querySelectorAll("input[type=checkbox][name=sales_location]");
    
    checkboxes.forEach(function(checkbox) {
    checkbox.addEventListener('change', function() {
        for (var cb of checkboxes) {  
            cb.removeAttribute('required');  
        }
    })
    });    
</script>

If the field is not required, nothing changes. But if it is, then the required attribute on the <input>is gone and all the checkboxes show up green, which is what I wanted.

It's not perfect because if the checkboxes become unchecked, they don't change back to red. So I am making a dirty assumption that if someone checked a box, they wouldn't go back and uncheck it and try to submit. In which case, the validation would show green (and unchecked) until Submit was pressed again, but then it would take them back to this field which would be red again. If you know how to improve my code by adding the different case for the change function (only if the field is required), please feel free to add that. Cheers.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 jfarzin