In a Django form, how do I render a radio button so that the choices are separated on the page?
Asked Answered
G

2

8

I have a Django form with a two-choice radio button element. I want the form to render more or less like this:

( ) I prefer beer
     The last sporting event I attended was: [           ]
     My favorite NASCAR driver is:           [           ]

( ) I prefer wine
     The last opera/play I attended was:     [           ]
     My favorite author is:                  [           ]

In other words, I want to split up the two radio button choices. How do I do that? Using the default form.as_table rendering, the choices are drawn right next to each other which I don't want.

(Apologies to NASCAR and opera enthusiasts.)

Gasoline answered 20/7, 2011 at 18:54 Comment(0)
L
5

I did a little digging, and found a lead in the answer to this similar post that points to a somewhat outdated post in django-users.

Borrowing some code from the as_widget() method in forms.py I fashioned a method that I could add to my form in order to retrieve the RadioSelect widget's choices rendered as HTML.

class MyForm(forms.Form):
    MY_CHOICES = (
        ('opt0', 'Option zero'),
        ('opt1', 'Option one'),
    )
    myfield = forms.ChoiceField(widget=forms.RadioSelect, choices=MY_CHOICES)

    def myfield_choices(self):
        """
        Returns myfield's widget's default renderer, which can be used to 
            render the choices of a RadioSelect widget.
        """
        field = self['myfield']
        widget = field.field.widget

        attrs = {}
        auto_id = field.auto_id
        if auto_id and 'id' not in widget.attrs:
            attrs['id'] = auto_id

        name = field.html_name

        return widget.get_renderer(name, field.value(), attrs=attrs)

Then in the template you can access individual radio button choices like so:

<ul>
    <li>
        {{ myform.myfield_choices.0 }}
        My custom HTML that goes with choice 0 
    </li>
    <li>
        {{ myform.myfield_choices.1 }}
        Different HTML that goes with choice 1 
    </li>
</ul>

or

{% for choice in myform.myfield_choices %}
    <div>
        {{ choice }}
    </div>
{% endfor %}

I'm pretty sure this is a bad idea. It will likely stop working at some point as django evolves. It violates DRY by copying code from as_widget(). (TBH, I didn't take the time to fully understand the code from as_widget) I'm using it as a temporary hack only. Perhaps there is a better way that involves custom template tags. If so, please let me know.

Lepley answered 27/7, 2011 at 23:22 Comment(0)
Y
4

The RadioSelect widget will render its output by default into an unordered list.

#forms.py
BEER = 0
WINE = 1
PREFERRED_DRINK_CHOICES = (
    (BEER, 'Beer'),
    (WINE, 'Wine'),
)

class DrinkForm(forms.Form):
    preferred_drink = forms.ChoiceField(choices=PREFERRED_DRINK_CHOICES,
                                        widget=forms.RadioSelect())

#views.py
def test(request):
    form = DrinkForm(request.POST or None)
    if request.method == 'POST':
        if form.is_valid():
            print 'form was valid'
    return render_to_response('test.html', {'form' : form},
        context_instance=RequestContext(request))

#test.html
<form action="." method="post" enctype="multipart/form-data">
    <ul>
        <li>
            {{ form.name.label_tag }}
            {{ form.name }}
            {{ form.name.errors }}
        </li>
        <li>
            {{ form.email.label_tag }}
            {{ form.email }}
            {{ form.email.errors }}
        </li>
        <li>
            {{ form.preferred_drink.label_tag }}
            {{ form.preferred_drink }}
            {{ form.preferred_drink.errors }}
        </li>
        <li>
        <input type="submit" value="Submit" />
        </li>
    </ul>
</form>

Will output:

<form enctype="multipart/form-data" method="post" action=".">
    <ul>
    <li>
            <label for="id_name">Name</label>
            <input type="text" maxlength="50" name="name" id="id_name">
    </li>
    <li>
        <label for="id_email">Email</label>
        <input type="text" id="id_email" name="email">
    </li>
    <li>
        <label for="id_preferred_drink_0">Preferred drink</label>
        <ul>
                    <li>
                        <label for="id_preferred_drink_0">
                            <input type="radio" name="preferred_drink" value="0" id="id_preferred_drink_0"> Beer</label>
                    </li>
                    <li>
                        <label for="id_preferred_drink_1"><input type="radio" name="preferred_drink" value="1" id="id_preferred_drink_1"> Wine</label>
                    </li>
                </ul>
        </li>
    <li>
        <input type="submit" value="Submit">
    </li>
    </ul>
</form>

That will render each radio choice on its own line. You'll probably need to add some CSS to get the rendering just the way you want, but that should get you the structure you need. Of course, you can shortcut writing the HTML by hand an just do...

<form action="." method="post">
    <ul>
        {{ form.as_ul }}
        <li>
            <input type="submit" value="Submit" />
        </li>
    </ul>
</form>

...but I prefer to write my HTML by hand.

Yeargain answered 20/7, 2011 at 21:12 Comment(5)
But that still prints the two choices on adjacent lines, right? That's not what I'm trying to do. I want to have the two radio buttons widely separated, with blocks of fields underneath each one, as in my nascar/opera example.Gasoline
If you write the HTML by hand, you can put additional fields or markup wherever you'd like. using form.as_ul will limit what you can do.Yeargain
What's the syntax for rendering a single choice of a multi-choice radio button element? {{form.preferred_drink}} renders all the choices.Gasoline
It's not super easy to get to a specific choice from the template level. You can iterate over the choices like a dictionary: {% for value, text in form.preferred_drink.field.choices %} {{ value }}, {{ text }} {% endfor %} however, indexing isn't supported at the template level to get to the choice you want. I would recommend setting the variable for the choice from the view, or writing a template tag to accept the field and the index of the choice you want and then render the input html.Yeargain
Just to add a newer Django feature. You can now get to the index. {{ form.preferred_drink.field.choices.0 }} will have ('val', 'label text'). So {{ form.preferred_drink.field.choices.0.0 }} will print the value, and {{ form.preferred_drink.field.choices.0.1 }} will print the label text.Arthurarthurian

© 2022 - 2024 — McMap. All rights reserved.