Django form with BooleanField always invalid unless checked

DjangoDjango Forms

Django Problem Overview


I have an application that uses the following form:

class ConfirmForm(forms.Form):
    account_name = forms.CharField(widget=forms.HiddenInput)
    up_to_date = forms.BooleanField(initial=True)

I use the form in the following template exerpt:

<form class="confirmform" action="/foo/" enctype="multipart/form-data" method="post">
{{ confirm_form.up_to_date }} Check if this data brings the account up to date.<br>
{{ confirm_form.account_name }} <input type="submit" name="confirm" value="Confirm" />
</form>

My view uses the following basic code structure:

if request.method == 'POST':
    #check for 'confirm' because I actually have multiple forms in this page
    if 'confirm' in request.POST:
        confirm_form = ConfirmForm(request.POST)
        if confirm_form.is_valid():
            #do stuff
        else:
            c['confirm_form'] = confirm_form
else:
    c['confirm_form'] = ConfirmForm({'account_name':'provided_value'})

Two things are wrong:

  1. Even though I have initial=True, the checkbox is unchecked when the page loads

  2. The form is always invalid unless I check the checkbox. It gives errors for up_to_date only: "This field is required."

I have read this similar question but his solution doesn't apply to my project.

So... what's going on?

Edit:

I updated the code above to be more faithful to my actual code.

Problem #1 was my fault because I was overriding the initial value by binding data when the form was instantiated.

Problem #2 I still consider an issue. Using required=False on up_to_date will fix the problem, however it doesn't seem correct that using the default widget, a BooleanField can be either NULL (causes validity check to fail) or True but never False.

Django Solutions


Solution 1 - Django

In Django forms, Boolean fields must be created with required=False.

When the checkbox isn't checked, browsers do not send the field in the POST parameters of requests. Without specifying that the field is optional Django will treat it as a missing field when not in the POST parameters.

Imho it would be nice for Django to have this behavior by default for boolean form fields..

(this was already answered in comments by Yuji but it would be useful as an answer)

Solution 2 - Django

initial=True should render the widget with checked="checked" so I don't see the problem there.. but the reason the form is invalid is because all fields are required by default unless you specify otherwise.

> Field.required¶ By default, each Field class assumes the value is > required, so if you pass an empty value -- either None or the empty > string ("") -- then clean() will raise a ValidationError exception:

If you'd like a checkbox or any other value to be optional, you need to pass in required=False into the form field constructor.

up_to_date = forms.BooleanField(initial=True, required=False) 
# no longer required..

Solution 3 - Django

According to official docs:

> If you want to include a boolean in your form that can be either True or False (e.g. a checked or unchecked checkbox), you must remember to pass in required=False when creating the BooleanField.

Solution 4 - Django

Just a hunch, but this could be about the basics - lost a few hours until realising something similar was just about basic variable types.

In my case, after creating the FORM (very basic, see below)

class para_selection(forms.Form):
    first=forms.BooleanField(initial=False, required=False)
    second=forms.BooleanField(initial=False, required=False)
    third=forms.BooleanField(initial=False, required=False)
    fourth=forms.BooleanField(initial=False, required=False)

... I was using if selection == 'True': .. in my view. But since the form is using Boolean values, the correct code would be: if selection is True:

The first line will never work, because "==" is not used with Boolean type. Hopefully this was not the case, but wanted to flag it here since I kept trying and searching all over the Internet, without paying attention to the type of variable I was using.

Solution 5 - Django

Make sure you are not overwriting the initial form the first time the page loads.

I originally wasn't checking if there was actually anything in request.GET. The first time the page loads request.GET is None, and calling SomeForm(request.GET) would actually wipe out the initial values.

def some_view(request):

    form = SomeForm()

    if request.method == 'GET':
        form = SomeForm(request.GET)

Adding a check to make sure there is something in request.GET fixed this.

def some_view(request):

    form = SomeForm()

    if request.method == 'GET' and request.GET:
        form = SomeForm(request.GET)

Solution 6 - Django

If you do as suggested, your up_to_date field will likely always be False. And you will never know if that is because the user wants it that way, or because the user skipped over the field!

I have the same problem with a legally required OPT_IN question. Where on signup, I need to get the user to make a conscience decision to allow my system to send them unsolicited e-mails (or not). I'm working with Django-Allauth and a custom user database.

I'll share my solution below in the hope you can adapt it to your situation - I adapted this from the DOCS.

forms.py:

# ExtraFieldsSignup as per https://stackoverflow.com/a/12308807
class SignupFormExtraFields(forms.Form):
  CHOICES = (('1', 'YES, keep in touch',), ('2', 'No',))
  first_name = forms.CharField(max_length=30, label='Voornaam')
  last_name = forms.CharField(max_length=30, label='Achternaam')
  opt_in = forms.ChoiceField(widget=forms.RadioSelect, choices=CHOICES)

  def signup(self, request, user):
    user.first_name = self.cleaned_data['first_name']
    user.last_name = self.cleaned_data['last_name']
    # Now we figure out what the user chose:
    if self.cleaned_data['opt_in'] == '1':
        user.opt_in = True
    else:
        user.opt_in = False
    user.save()

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiondhowlandView Question on Stackoverflow
Solution 1 - DjangoyairchuView Answer on Stackoverflow
Solution 2 - DjangoYuji 'Tomita' TomitaView Answer on Stackoverflow
Solution 3 - DjangouserView Answer on Stackoverflow
Solution 4 - DjangoAdrian StanicaView Answer on Stackoverflow
Solution 5 - DjangoSuperFunkyMonkeyView Answer on Stackoverflow
Solution 6 - DjangoRichard CookeView Answer on Stackoverflow