Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.)

like image 996
John Gordon Avatar asked Jul 20 '11 18:07

John Gordon


2 Answers

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.

like image 149
Cole Avatar answered Sep 21 '22 13:09

Cole


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.

like image 43
Brandon Avatar answered Sep 20 '22 13:09

Brandon