There's a great explanation here on adding attributes to <option> tags within a django form field. But this is only for a Select widget. I want to do the same for a SelectMultiple widget.
I tried the following (subclass Select and SelectMultiple and reference MySelectMultiple when creating the Model form field employees):
class MySelect(forms.Select):
def __init__(self, *args, **kwargs):
super(MySelect, self).__init__(*args, **kwargs)
def render_option(self, selected_choices, option_value, option_label):
# original forms.Select code #
return u'<option custom_attribute="foo">...</option>'
class MySelectMultiple(MySelect):
def __init__(self, *args, **kwargs):
super(MySelectMultiple, self).__init__(*args, **kwargs)
employees = forms.ModelMultipleChoiceField(
widget=MySelectMultiple(attrs={}),
queryset=Employee.objects.all(),
)
But the rendered form is still showing as a Select widget, not a SelectMultiple widget.
I can provide attrs={'multiple':'multiple'} to MySelectMultiple to make the form field render as a multiple select widget - but when the form is saved, only a single value is saved (not multiple values)!
How can I have the form render as a multiple choice field AND save all of the selected values? Thank you.
Your select multiple should inherit from SelectMultiple rather than Select:
class MySelectMultiple(SelectMultiple):
def render_option(self, selected_choices, option_value, option_label):
# original forms.Select code #
return u'<option custom_attribute="foo">...</option>'
It looks like your __init__ method isn't necessary, since it just calls super().
SelectMultiple inherits from Select, you just need to override the create_option method to get the same results:
class MySelectMultiple(forms.SelectMultiple):
def create_option(self, name, value, *args, **kwargs):
option = super().create_option(name, value, *args, **kwargs)
if value:
instance = self.choices.queryset.get(pk=value) # get instance
option['attrs']['custom_attr'] = instance.field_name # set option attribute
return option
Alright, figured out: by using:
class MySelectMultiple(MySelect):
def __init__(self, *args, **kwargs):
super(MySelectMultiple, self).__init__(*args, **kwargs)
I was still using the django native Select but just calling it MySelectMultiple. I needed to import the rest of SelectMultiple.
So, instead of:
class MySelectMultiple(MySelect):
def __init__(self, *args, **kwargs):
super(MySelectMultiple, self).__init__(*args, **kwargs)
The class needs to be complete (copy/paste of native django code for SelectMultiple):
class MySelectMultiple(MySelect):
allow_multiple_selected = True
def __init__(self, *args, **kwargs):
super(MySelectMultiple, self).__init__(*args, **kwargs)
def render(self, name, value, attrs=None, choices=()):
if value is None:
value = []
final_attrs = self.build_attrs(attrs, name=name)
output = [format_html('<select multiple="multiple"{0}>', forms.utils.flatatt(final_attrs))]
options = self.render_options(choices, value)
if options:
output.append(options)
output.append('</select>')
return mark_safe('\n'.join(output))
def value_from_datadict(self, data, files, name):
if isinstance(data, (MultiValueDict, MergeDict)):
return data.getlist(name)
return data.get(name, None)