I was able to use the same technique given in this answer. It works perfectly for CheckboxSelectMultiple although it is not used in the answer.
I saved this in my project's forms.py:
from django.forms.widgets import CheckboxSelectMultiple, Select
class SelectWithAttrs(Select):
"""
Select With Option Attributes:
Subclass of Django's Select widget that allows attributes in options,
e.g. disabled="disabled", title="help text", class="some classes",
style="background: color;", etc.
Pass a dict instead of a string for its label:
choices = [ ('value_1', 'label_1'),
...
('value_k', {'label': 'label_k', 'foo': 'bar', ...}),
... ]
The option k will be rendered as:
<option value="value_k" foo="bar" ...>label_k</option>
"""
def create_option(self, name, value, label, selected, index,
subindex=None, attrs=None):
if isinstance(label, dict):
opt_attrs = label.copy()
label = opt_attrs.pop('label')
else:
opt_attrs = {}
option_dict = super().create_option(
name, value, label, selected, index,
subindex=subindex, attrs=attrs)
for key, val in opt_attrs.items():
option_dict['attrs'][key] = val
return option_dict
class CheckboxSelectMultipleWithAttrs(
SelectWithAttrs, CheckboxSelectMultiple):
pass
Here is a working snippet from a project of mine that uses this example. The stuff in the beginning isn't really important, but it shows how to build your attributes dict and pass it into your choices.
from django import forms
from django.whatever import other_stuff
from project_folder import forms as project_forms
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ['employees']
def __init__(self, *args, **kwargs)
super().__init__(*args, **kwargs)
self.fields['employees'].queryset =\
self.company.employee_set.filter(is_active=True)
existing_crews_employees = []
for crew in existing_job_crews:
crew_employees =\
[employee.__str__() for employee in crew.employees.all()]
existing_crews_employees.append({'crew_name': crew.crewtype.name,
'employees': crew_employees})
employees_choices = []
for (index, choice) in enumerate(self.fields['employees'].choices):
# loop over each choice and set proper paramaters for employees
# that are unassigned/on the current crew/on a different crew
employee_in_crew = False
employee_name = choice[1]
for crew_and_employees in existing_crews_employees:
for employee in crew_and_employees['employees']:
if employee_name == employee.__str__():
crew_name = crew_and_employees['crew_name']
if self.obj and self.obj.crewtype.name == crew_name:
# check the box if employee in current crew
employees_choices.append((choice[0], {
'label': choice[1],
'checked': True,
'id': f'id_employees_{choice[0].instance.id}'
}))
else:
# disable the choice if employee in another crew
employees_choices.append((choice[0], {
'label':
employee_name + f" (on Crew: {crew_name})",
'disabled': True}))
employee_in_crew = True
# for valid entries, ensure that we pass the proper ID
# so that clicking the label will also check the box
if not employee_in_crew:
employees_choices.append((choice[0], {
'label': choice[1],
'id': f'id_employees_{choice[0].instance.id}'}))
self.fields['employees'].widget = project_forms.CheckboxSelectMultipleWithAttrs(
choices=employees_choices)
There are two important things to keep in mind when using this technique:
- Ensure that you pass the
id into your attrs for your clickable options, otherwise your labels will not check the proper boxes when they are clicked.
- This method currently requires initial values to be set using the new attributes dict. Ensure that you pass the
'checked': True key-value pair to any boxes that should be checked.