How do I iterate over the options of a SelectField in a template?
Asked Answered
C

6

34

I have a select field in the form and now I need to iterate over options in this field.

{{ form.myselect }} gives me this:

<select name="myselect" id="id_myselect">
    <option value="" selected="selected">---------</option>
    <option value="2">Item 1</option>
    <option value="3">Item 2</option>
    ...
</select>

Now I need to add some attributes to the options and because of that what I need is:

<select name="myselect" id="id_myselect">
{% for x in form.myselect %}
    <option value="{{ x.id }}">{{ x.name }}</option>
{% endfor %}
</select>

but there is an error:

Caught TypeError while rendering: 'BoundField' object is not iterable

I tried form.myselect.all, form.myselect.option_set but it gives nothing

Crane answered 10/2, 2012 at 0:20 Comment(6)
So what you want is all the <option> without the <select> and no blank (-----) option? or did I miss something? ... What are you trying to achieve specifically here?Caritta
No I want to add some atributes to the options and because of that need it in the ` {% for x in form.myselect %}` loop somehow.Crane
My suggestion would be to alter the widget and do it in the code: docs.djangoproject.com/en/dev/ref/forms/widgetsCaritta
Thanks James. I was hope that there is some way to iterate over options in template.Crane
#6478356Caritta
Thats something that really needs to be in the widget. IMHO its the right place also.Caritta
R
96

I've been struggling with this problem today and found the solution. Yes, you can iterate over options of the select tag directly in template. Here's how to do it in template:

<select id="id_customer" name="customer">
{% for x, y in form.fields.customer.choices %}
    <option value="{{ x }}"{% if form.fields.customer.value == x %} selected{% endif %}>{{ y }}</option>
{% endfor %}
</select>

In this case I have a customer field in the form which has choices set up as follows:

class SomeForm(forms.Form):
    customer = forms.ChoiceField(label=u'Customer')

    def __init__(self, *args, **kwargs):
        super(SomeForm, self).__init__(*args, **kwargs)
        self.fields['customer'].choices = [(e.id, e.customer) for e in Customers.objects.all()]
Rileyrilievo answered 23/5, 2012 at 7:34 Comment(2)
I had the same issue and had success without changing anything else but adding: {% for c in form.fields.customer.queryset %}<option value="{{ c.id }}">{{ c.name|capfirst }}</option>{% endfor %}Mollescent
To set the selected didn't work for me. Instead of form.fields.Customer.value I had to use form.initial.CustomerDesk
B
30

Got it to work with:

    <select name="myselect" class="i-can-add-my-own-attrs-now" id="id_myselect">
        {% for id, name in form.myselect.field.choices %}
        <option value="{{ id }}">{{ name }}</option>
        {% endfor %}
    </select>

BUT REALLY, a better way to do this is with django-widget-tweaks:

    {% load widget_tweaks %}
    {{ form.myselect|add_class:"i-can-haz-custom-classes-easily" }}

Doing it with django-widget-tweaks will also set the default 'selected="selected"' for you, which is super nice!

Burdett answered 19/4, 2013 at 23:6 Comment(1)
but this only adds the class to a <select> itself not each option field. Is it possible to iterate through choices with this plugin?Deckhand
L
9

I do this way:

<select id="id_construction_type" name="construction_type" class="form-control input-md">
{% for value, key in form_urban.fields.construction_type.choices %}
    <option value="{{ value }}"{% if form_urban.initial.construction_type == value %} selected {% endif %}>
        {{ key }}
    </option>
{% endfor %}
</select>
Lieb answered 13/4, 2015 at 16:48 Comment(0)
T
4

This is a cleaner solution, you can set the attributes using a custom Widget. This way you don't have to render the field manually:

class CustomSelectWidget(forms.Select):
    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

class SomeForm(forms.ModelForm):
    some_field = forms.ModelChoiceField(
        queryset=SomeModel.objects.all(),
        widget=CustomSelectWidget
    )
Telson answered 12/3, 2019 at 20:8 Comment(0)
E
1

I've run into this issue several times and wanted to add an additional situation where you are using Django filters and setting choices that are related to a FieldFilter you've defined inside the FilterSet class.

class CustomFilter(django_filters.FilterSet):
    your_field = django_filters.CharFilter(
        field_name='your_field',
        lookup_expr='iexact',
        widget=forms.Select(
            choices=[(x, x) for x in some_list_of_choices]
        )
    )

The other solutions, rely on a .choices attribute on the field class but they did not work for me. Instead I iterated through the your_field attr on the Form associated with the FilterSet.

This allows you to easily set your own default and assign any desired classes to the select element.

<select name="your_field">
    <option value="" selected>Your Field</option>
    {% for option in filter.form.your_field %}
        {{ option }}
    {% endfor %}

</select>
Eclair answered 31/5, 2023 at 14:40 Comment(0)
D
0

With radio buttons in your template use.

    <table>
        {% for x,y in form.fields.Customer.choices %}
        <tr>
            <td><input id="id_Customer_{{x}}" {% if form.fields.Customer.value == x %}checked="checked"{% endif %} name="Customer" type="radio" value="{{x}}" /></td>
            <td>{{ y }}</td>
        </tr>
        {% endfor %}
    </table>
Dramatist answered 29/5, 2014 at 16:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.