# Model Choice Field - how to add attributes to the options

**URL:** <https://forum.djangoproject.com/t/model-choice-field-how-to-add-attributes-to-the-options/37782>\
**Category:** Forms & APIs\
**Created:** [January 8, 2025, 4:22pm UTC](https://forum.djangoproject.com/t/model-choice-field-how-to-add-attributes-to-the-options/37782 "2025-01-08T16:22:58Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![m-weintraub](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/m-weintraub/32/20002_2.png) [@m-weintraub](https://forum.djangoproject.com/u/m-weintraub)\
**Post date:** [January 8, 2025, 4:22pm UTC](https://forum.djangoproject.com/t/model-choice-field-how-to-add-attributes-to-the-options/37782/1 "2025-01-08T16:22:58Z")

</div>

I want to add an attribute to model choice field on a form. The intent to do some javascript like hide or show based on a filter. For example, suppose there’s a two state status on each choice, like _active_ or _inactive_. These are checkboxes, which `onChange` should make choices with appear or disappear based on whether the checkbox is checked. The attribute to add to each option is generated on each page load, as they are transient. We’re not using APIs for this app just yet.

I’ve been playing around with different solutions I’ve found on-line. They all hinge on extending `widget.Select` to add the custom field to the attrs. After trying several variations, I found the right search phrase that led me to a solution in the django doc: [https://docs.djangoproject.com/en/4.2/ref/forms/fields/#modelmultiplechoicefield](https://ModelMultipleChoiceField). Unfortunately, what’s in the doc isn’t what I’m seeing.

```auto
from django import forms

class ToppingSelect(forms.Select):
    def create_option(
        self, name, value, label, selected, index, subindex=None, attrs=None
    ):
        option = super().create_option(
            name, value, label, selected, index, subindex, attrs
        )
        if value:
            option["attrs"]["data-price"] = value.instance.price
        return option

class PizzaForm(forms.ModelForm):
    class Meta:
        model = Pizza
        fields = ["topping"]
        widgets = {"topping": ToppingSelect}

```

So I adapted the solution:

```auto
class CustomSelectWithAttrs(forms.Select):

    def create_option(
        self, name, value, label, selected, index, subindex=None, attrs=None
    ):
        
        option = super().create_option(
            name, value, label, selected, index, subindex, attrs
        )
        print(f'option = {option}')
        print(f'\tattrs = {attrs}')
        print(f'\tvalue = {value} instance={value.instance}')
        if value:
            print(f'\tstatus = {value.instance.status}')
            option["attrs"]['current-status'] = value.instance.status.state

        print(f'all set now: option = {option}')
        return option

```

Here’s the form

```auto
class MatchFilterForm(ModelForm):
    class Meta:
        model = Match
        fields = {'tutor', 'student', 'match_status', 'date_started', 'comments'}
        exclude = {'lvm_affiliate', 'date_dissolved', }
        labels = {'tutor': 'Tutor pool',
                  'student': 'Student pool',
                  'match_status': "Status",
                  'date_started': "Start date",
                  'comments': 'Comments'}
        widgets = {
            'date_started': DateInput(
                format='%Y-%m-%d',
                attrs={
                    'class': 'form-control',
                    'placeholder': 'Select a date',
                    'type': 'date'
                }
            ),
            'student': CustomSelectWithAttrs,
        }

```

This renders to

 ![image](https://cdck-file-uploads-us1.s3.dualstack.us-west-2.amazonaws.com/flex026/uploads/djangoproject/original/3X/8/6/865f35fbc1686edf23627ed68331e84d8d962d62.png)  
which is great. The html is not so great - the attribute isn’t there:  
 ![image](https://cdck-file-uploads-us1.s3.dualstack.us-west-2.amazonaws.com/flex026/uploads/djangoproject/original/3X/7/8/7862d702e88284977e07ee7f0c4181937271c54f.png)  
I’m confused because it looks like the widget’s `create_option` is working:  
 ![image](https://cdck-file-uploads-us1.s3.dualstack.us-west-2.amazonaws.com/flex026/uploads/djangoproject/original/3X/7/4/74ad538fbefc501478463df976ff8a4bf25dbd13.png)  
The option initializes in the line that starts _option =_ and `attrs = {}`  
The custom attribute are in in attrs in the line that _starts all set now_.

---

<div class="post-metadata">

**Author:** ![anefta](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/anefta/32/10649_2.png) [@anefta](https://forum.djangoproject.com/u/anefta)\
**Post date:** [January 11, 2025, 1:30am UTC](https://forum.djangoproject.com/t/model-choice-field-how-to-add-attributes-to-the-options/37782/2 "2025-01-11T01:30:51Z")

</div>

> [@m-weintraub](#):
>
> ```auto
> class CustomSelectWithAttrs(forms.Select):
> 
> def create_option(
> self, name, value, label, selected, index, subindex=None, attrs=None
> ):
>         
> option = super().create_option(
> name, value, label, selected, index, subindex, attrs
> )
> print(f'option = {option}')
> print(f'\tattrs = {attrs}')
> print(f'\tvalue = {value} instance={value.instance}')
> if value:
> print(f'\tstatus = {value.instance.status}')
> option["attrs"]['current-status'] = value.instance.status.state
> 
> print(f'all set now: option = {option}')
> return option
> 
> ```

In your `CustomSelectWithAttrs` class, you are trying to access `value.instance` which doesn’t exist because `value` is just the id of the instance and not the instance itself. You need to get the instance associated with the choice.

```auto
class CustomSelectWithAttrs(forms.Select):
    def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
        option = super().create_option(name, value, label, selected, index, subindex, attrs)
        if value:
            instance = self.choices.queryset.get(id=value)  
            option["attrs"]['current-status'] = instance.status.state 
        return option

```

---

<div class="post-metadata">

**Author:** ![m-weintraub](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/m-weintraub/32/20002_2.png) [@m-weintraub](https://forum.djangoproject.com/u/m-weintraub)\
**Post date:** [January 11, 2025, 4:43pm UTC](https://forum.djangoproject.com/t/model-choice-field-how-to-add-attributes-to-the-options/37782/3 "2025-01-11T16:43:16Z")

</div>

@anefta : Thanks for offering a solution. Unfortunately, I am unable to get it to work. I can confirm the suggestion does get the right instance. It seems there is something else at play, that the `attrs` key/value pair is being ignored elsewhere.

I changed the code per the suggestion.

```auto
 def create_option(
        self, name, value, label, selected, index, subindex=None, attrs=None
    ):

        option = super().create_option(
            name, value, label, selected, index, subindex, attrs
        )
        print(f'\tvalue = {value} instance={value.instance}')
        if value:
            instance = self.choices.queryset.get(id=value.value)
            # option["attrs"]['current-status'] = value.instance.status.state
            if instance.status is None:
                option["attrs"]["current-status"] = "no-status"
            else:
                option["attrs"]['current-status'] = instance.status.state

        pprint(option)
        return option

```

For getting the instance out of the choices.queryset, because it’s a ModelChoicesIterator, to get the field value for the filter, I had to use `value.value`. Unfortunately, the result is the same, `attrs` appears properly populated, but the attribute doesn’t end up in the rendering. Here’s an example of the value of `option` at the end of a call:

```auto
{'attrs': {'current-status': 'prospect'},
 'index': '23',
 'label': 'a b',
 'name': 'student',
 'selected': False,
 'template_name': 'django/forms/widgets/select_option.html',
 'type': 'select',
 'value': <django.forms.models.ModelChoiceIteratorValue object at 0x109998950>,
 'wrap_label': True}

```

As an experiment, I tried reassigning option[“value”] = value.value, the end result is the same even though option is

```auto
{'attrs': {'current-status': 'prospect'},
 'index': '23',
 'label': 'a b',
 'name': 'student',
 'selected': False,
 'template_name': 'django/forms/widgets/select_option.html',
 'type': 'select',
 'value': 81,
 'wrap_label': True}

```

After the initial call to `option = super().create_option(name, value, label, selected, index, subindex, attrs)`

```auto
{'attrs': {},
 'index': '23',
 'label': 'a b',
 'name': 'student',
 'selected': False,
 'template_name': 'django/forms/widgets/select_option.html',
 'type': 'select',
 'value': <django.forms.models.ModelChoiceIteratorValue object at 0x114267710>,
 'wrap_label': True}

```

So it looks as though my specialization is working. Is there something else I need to do that might be a feature of the `Select` object like I had to do with the `ModelChoiceIteratorValue`?

As an aside, I do have a question about the explanation. I get that `value` isn’t an object. But then why does one of the print statements work? The third print statement is `print(f'\tvalue = {value} instance={value.instance}')` which in the console prints out

 ![image](https://us1.discourse-cdn.com/flex026/uploads/djangoproject/original/3X/7/4/74ad538fbefc501478463df976ff8a4bf25dbd13.png) (the indented line _value = 1 instance=Suzied Q-Tips_

I can affirm this is what you’d expect as output for the instance whose id equals 1 (`value`). Because this matches what I presumed the behavior is from the django doc example, I didn’t think I needed to get the instance from the queryset.

---

<div class="post-metadata">

**Author:** ![anefta](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/anefta/32/10649_2.png) [@anefta](https://forum.djangoproject.com/u/anefta)\
**Post date:** [January 12, 2025, 11:33am UTC](https://forum.djangoproject.com/t/model-choice-field-how-to-add-attributes-to-the-options/37782/4 "2025-01-12T11:33:36Z")

</div>

Please ask one question at a time for clarity and avoid images. Use code tags for your full Python and HTML code.

> [@m-weintraub](#):
>
> `pprint(option)`

Paste the output as it is - not image.

---

<div class="post-metadata">

**Author:** ![m-weintraub](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/m-weintraub/32/20002_2.png) [@m-weintraub](https://forum.djangoproject.com/u/m-weintraub)\
**Post date:** [January 14, 2025, 1:55pm UTC](https://forum.djangoproject.com/t/model-choice-field-how-to-add-attributes-to-the-options/37782/5 "2025-01-14T13:55:44Z")

</div>

OK. I’ll follow your advice for how to include results in the future.

But, the issue still remains and I’d appreciate help getting my django code to work as expected. I’m sure it’s something I’m doing. Thanks.

---

<div class="post-metadata">

**Author:** ![anefta](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/anefta/32/10649_2.png) [@anefta](https://forum.djangoproject.com/u/anefta)\
**Post date:** [January 14, 2025, 2:58pm UTC](https://forum.djangoproject.com/t/model-choice-field-how-to-add-attributes-to-the-options/37782/6 "2025-01-14T14:58:32Z")

</div>

> [@m-weintraub](#):
>
> pprint(option)

What is the output? Please paste here

---

<div class="post-metadata">

**Author:** ![m-weintraub](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/m-weintraub/32/20002_2.png) [@m-weintraub](https://forum.djangoproject.com/u/m-weintraub)\
**Post date:** [January 14, 2025, 3:13pm UTC](https://forum.djangoproject.com/t/model-choice-field-how-to-add-attributes-to-the-options/37782/7 "2025-01-14T15:13:41Z")

</div>

```auto
{'attrs': {'current-status': 'prospect'},
 'index': '22',
 'label': 'harrells ice cream',
 'name': 'student',
 'selected': False,
 'template_name': 'django/forms/widgets/select_option.html',
 'type': 'select',
 'value': <django.forms.models.ModelChoiceIteratorValue object at 0x10d48f950>,
 'wrap_label': True}

```

I’ve also tried setting `'value': value.value` instead of the `ModelChoiceIteratorValue`.

---

<div class="post-metadata">

**Author:** ![anefta](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/anefta/32/10649_2.png) [@anefta](https://forum.djangoproject.com/u/anefta)\
**Post date:** [January 15, 2025, 10:50am UTC](https://forum.djangoproject.com/t/model-choice-field-how-to-add-attributes-to-the-options/37782/8 "2025-01-15T10:50:15Z")

</div>

> [@m-weintraub](#):
>
> ough option is

Where is your html template code where you render this form?

---

<div class="post-metadata">

**Author:** ![m-weintraub](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/m-weintraub/32/20002_2.png) [@m-weintraub](https://forum.djangoproject.com/u/m-weintraub)\
**Post date:** [January 15, 2025, 12:53pm UTC](https://forum.djangoproject.com/t/model-choice-field-how-to-add-attributes-to-the-options/37782/9 "2025-01-15T12:53:12Z")

</div>

```auto
<div>
                <form action="" method="post">
                  {% csrf_token %}

                  {{ form.tutor|as_crispy_field }}
                  {{ form.student|as_crispy_field }}
                  { form.new_student|as_crispy_field }
                  {{ form.match_status|as_crispy_field }}
                  {{ form.date_started|as_crispy_field }}
                  {{ form.comments|as_crispy_field }}

                  <table>
                    <tr>
                      <td class="col-2">
                        <input type="submit" value="Save" class="btn btn-primary btn-sm offset-2">
                      </td>
                      <td class="col-2">
                        <input type="reset" value="Reset" class="btn btn-secondary btn-sm offset-2">
                      </td>
                      <td class="col-2">
                        <a href="{% url page_context.cancel_button %}" class="btn btn-danger btn-sm offset-2">
                          Cancel
                        </a>
                      </td>
                    </tr>
                  </table>
                </form>
              </div>

```

and in case you will want the form’s `crispy` code, here it is.

```auto
        self.helper = FormHelper(self)

        self.helper.form_id = 'get-match'
        self.helper.form_method = 'post'
        self.helper.form_tag = False
        self.helper.form_show_labels = True
        self.helper.form_class = 'form-horizontal'
        self.helper.label_class = 'col-3'
        self.helper.field_class = 'col-6'

```

---

<div class="post-metadata">

**Author:** ![anefta](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/anefta/32/10649_2.png) [@anefta](https://forum.djangoproject.com/u/anefta)\
**Post date:** [January 15, 2025, 1:58pm UTC](https://forum.djangoproject.com/t/model-choice-field-how-to-add-attributes-to-the-options/37782/10 "2025-01-15T13:58:23Z")

</div>

> [@m-weintraub](#):
>
> pprint(option)

My guess is that is a crispy forms issue?! [About Custom Form Attributes using Django Crispy Forms - DjangoTricks](https://www.djangotricks.com/tricks/c7Bk8ZYvNLjD/)

Another approach could be to create a custom template for the select option: `templates/widgets/custom_select_option.html`

```auto
<option value="{{ widget.value }}"
        {% for name, value in widget.attrs.items %}
            {{ name }}="{{ value }}"
        {% endfor %}
        {% if widget.selected %}selected{% endif %}>
    {{ widget.label }}
</option>

```

Then in CustomSelectWithAttrs:

```auto
class CustomSelectWithAttrs(forms.Select):
    option_template_name = 'widgets/custom_select_option.html'

    def create_option(
        self, name, value, label, selected, index, subindex=None, attrs=None
    ):
        option = super().create_option(
            name, value, label, selected, index, subindex, attrs
        )
        if value and hasattr(value, 'instance'):
            try:
                option["attrs"]['data-current-status'] = value.instance.status.state
            except AttributeError:
                pass # Handle cases where the instance might not have the expected attributes
        return option

```

And now you could select options based on the data-\* attribute:

```auto
document.querySelectorAll('select[name="student"] option[data-current-status="active"]')

```

---

<div class="post-metadata">

**Author:** ![m-weintraub](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/m-weintraub/32/20002_2.png) [@m-weintraub](https://forum.djangoproject.com/u/m-weintraub)\
**Post date:** [January 19, 2025, 10:30pm UTC](https://forum.djangoproject.com/t/model-choice-field-how-to-add-attributes-to-the-options/37782/11 "2025-01-19T22:30:38Z")

</div>

thanks. This is a good solution. Not sure why we had to do all this, but at least it’s done.

Much obliged and thanks for all the patience.

---

<div class="post-metadata">

**Author:** ![anefta](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/anefta/32/10649_2.png) [@anefta](https://forum.djangoproject.com/u/anefta)\
**Post date:** [January 20, 2025, 10:13am UTC](https://forum.djangoproject.com/t/model-choice-field-how-to-add-attributes-to-the-options/37782/12 "2025-01-20T10:13:58Z")

</div>

I am glad it worked. Please mark the reply that helped you as a solution in order to also help others. 🙂
