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

Goran
  • 6,644
  • 11
  • 34
  • 54
  • So what you want is all the ` – James Khoury Feb 10 '12 at 00:24
  • 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. – Goran Feb 10 '12 at 00:31
  • 2
    My suggestion would be to alter the widget and do it in the code: https://docs.djangoproject.com/en/dev/ref/forms/widgets/ – James Khoury Feb 10 '12 at 00:33
  • Thanks James. I was hope that there is some way to iterate over options in template. – Goran Feb 10 '12 at 00:41
  • 1
    http://stackoverflow.com/questions/6477856/how-to-add-attributes-to-option-tags-in-django – James Khoury Feb 10 '12 at 00:52
  • Thats something that really needs to be in the widget. IMHO its the right place also. – James Khoury Feb 10 '12 at 00:54

6 Answers6

94

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()]
starball
  • 20,030
  • 7
  • 43
  • 238
Jaro
  • 1,232
  • 12
  • 12
  • 2
    I had the same issue and had success without changing anything else but adding: `{% for c in form.fields.customer.queryset %}{% endfor %}` – Paolo Feb 25 '13 at 17:15
  • 1
    To set the selected didn't work for me. Instead of `form.fields.Customer.value` I had to use `form.initial.Customer` – rain01 Jun 01 '16 at 23:30
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!

epylinkn
  • 1,128
  • 12
  • 16
  • but this only adds the class to a – pasevin Oct 26 '15 at 11:49
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>
Cícero Alves
  • 153
  • 1
  • 2
  • 6
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
    )
p14z
  • 1,760
  • 1
  • 11
  • 17
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>
qosha
  • 11
  • 1
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>
user1491229
  • 683
  • 1
  • 9
  • 14