Opened 13 months ago

Last modified 13 months ago

#27445 assigned New feature

RadioSelect widget does not work for NullBooleanField

Reported by: Anne Fleischer Owned by: Jacob Rief
Component: Forms Version: master
Severity: Normal Keywords: NullBooleanField, RadioSelect
Cc: Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: yes
Easy pickings: no UI/UX: no

Description

The default widget for NullBooleanFields result in a selectbox with 3 options being shown.
In my opinion it should also be possible to represent those 3 options by radio inputs.
However, simply changing the form widget for a NullBooleanField to be a RadioSelect, will result in no form inputs whatsoever being shown.

Here is the code to reproduce it:

from django.db import models

class MyTestModel(models.Model):
    foo = models.NullBooleanField(blank=True, null=True)
from django import forms

class MyTestModelForm(forms.ModelForm):
    class Meta:
        model = MyTestModel
        fields = ('foo',)
        widgets = {
            'foo': forms.RadioSelect,
        }

I'm not sure if this is a bug or if the bahaviour is intentional.
As a workaround I tried to just initialize the choices manually in the widget, like this:

widgets = {
            'foo': forms.RadioSelect(choices=(('', 'Unknown'), ('1', 'Yes'), ('0', 'No'))),
        }

However, this works in terms of storing the correct value, but it does not render the selected radio input as checked.

I'd like to work in this if someone can confirm that this behaviour is not intended.

Change History (10)

comment:1 Changed 13 months ago by Takis Issaris

Hi,

I can confirm that specifying the widget removes all input/select tags:

from app27445.forms import MyTestModelForm
str(MyTestModelForm())
<tr><th><label for="id_foo">Foo:</label></th><td><select id="id_foo" name="foo">\n<option value="1" selected>Unknown</option>\n<option value="2">Yes</option>\n<option value="3">No</option>\n</select></td></tr>
from app27445.forms import MyTestModelForm
str(MyTestModelForm())
<tr><th><label for="id_foo_0">Foo:</label></th><td><ul id="id_foo"></ul></td></tr>

But I do not think this is specific to NullBooleanField as I get the same output when using the BooleanField.

comment:2 Changed 13 months ago by Kenneth Veldman

Owner: changed from nobody to Kenneth Veldman
Status: newassigned

comment:3 Changed 13 months ago by Kenneth Veldman

Triage Stage: UnreviewedAccepted

Was able to reproduce using the same model and forms. I added a couple of more checks by using multiple fields like so:

class MyTestModel(models.Model):
    foo = models.NullBooleanField(blank=True, null=True)
    bar = models.BooleanField(blank=True)
    baz = models.BooleanField()
    bor = models.NullBooleanField()

All results in an empty form when using the forms.RadioSelect widget.

comment:4 Changed 13 months ago by Jacob Rief

Owner: changed from Kenneth Veldman to Jacob Rief

comment:5 Changed 13 months ago by Tim Graham

Not sure how much effort the fix is here, but for what it's worth, there's a proposal that might involve deprecating NullBooleanField (#23130). Completing that ticket might be more fruitful in the long run.

comment:6 Changed 13 months ago by Takis Issaris

After digging through the code for quite some time, I decided to cheat a little and have another go at search StackOverflow.

This is what I found regarding the saved value not being reflected in the rendered form (in an UpdateView for example).

It was suggested http://stackoverflow.com/a/28262878 that one should use the following choices to make it work:

choices=((None, 'N/A'), (True, 'Yes'), (False, 'No'))

That works for me for True and False, but not for the None value of the NullBooleanField.

On the same thread a user named 'x0nix' suggests this snippet http://stackoverflow.com/a/28906939:

class NullBooleanRadioSelect(RadioSelect):
    def __init__(self, *args, **kwargs):
        choices = (
            (None, ugettext_lazy('Unknown')),
            (True, ugettext_lazy('Yes')),
            (False, ugettext_lazy('No'))
        )
        super(NullBooleanRadioSelect, self).__init__(choices=choices, *args, **kwargs)

    _empty_value = None
...
        widgets = {'foo': NullBooleanRadioSelect()}

With this snippet all three values seem to be rendered correctly.

comment:7 Changed 13 months ago by Takis Issaris

After looking at the empty_value in the forms.widgets.RadioSelect, I tried using the following for choices:

        choices = (('', 'Unknown'), (True, 'Yes'), (False, 'No'))

With this the issue of the HTML rendering not selecting the saved value is gone, so please ignore my previous post...

comment:8 Changed 13 months ago by Takis Issaris

Should the need for specifying the choices field as mentioned above be documented?
Or should the behaviour be modified so that the default choices field is set as in the previous post automatically?

comment:9 Changed 13 months ago by Jacob Rief

While #23130 "BooleanField should not override 'blank' if choices are specified", might make sense on the long term, this report describes a real bug.
I spoke with various people on the sprints, which had their concerns about fixing #23130, so even if at any time NullBooleanField is removed, it does not harm to fix this ticket.

Last edited 13 months ago by Tim Graham (previous) (diff)

comment:10 Changed 13 months ago by Tim Graham

Has patch: set
Patch needs improvement: set

Repeating my comment from the PR, we're targeting template-based widget rendering (#15667) for 1.11 which conflicts with this patch because things like RendererMixin are removed. I think the widget would just need to be tweaked based on the changes in that branch, but I want to look at ticket #23130 to see if that idea might obsolete this fix so that we don't have to add a widget only to deprecated it later.

I wonder if a documentation suggestion of how to make things work without this widget could suffice for now, which would also help users of older Django versions.

Also, it would be nice if you could summarize the concerns about fixing #23130 that you discussed at the sprint.

Note: See TracTickets for help on using tickets.
Back to Top