web-dev-qa-db-fra.com

Le formulaire Django avec BooleanField est toujours invalide s'il n'est pas coché

J'ai une application qui utilise le formulaire suivant:

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

J'utilise le formulaire dans l'extrait de modèle suivant:

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

Mon affichage utilise la structure de code de base suivante:

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'})

Deux choses ne vont pas:

1) Même si initial = initial = True, la case à cocher est décochée lors du chargement de la page.

2) Le formulaire est toujours invalide, sauf si je coche la case. Il ne donne que des erreurs pour up_to_date: "Ce champ est obligatoire."

J'ai lu cette question similaire mais sa solution ne s'applique pas à mon projet.

Alors que se passe-t-il?

Modifier:

J'ai mis à jour le code ci-dessus pour être plus fidèle à mon code actuel.

Le problème n ° 1 était de ma faute parce que je remplaçais la valeur initiale en liant des données lorsque le formulaire était instancié.

Problème n ° 2 Je considère toujours un problème. L'utilisation de required=False sur up_to_date résoudra le problème, mais il ne semble pas correct qu'en utilisant le widget par défaut, un BooleanField puisse être soit NULL (provoque l'échec du contrôle de validité) ou True mais jamais False.

43
dhowland

initial=True devrait rendre le widget avec checked="checked" donc je ne vois pas le problème ici .. mais la raison pour laquelle le formulaire est invalide est que tous les champs sont obligatoires par défaut, sauf indication contraire de votre part. 

Field.required¶ Par défaut, chaque classe de champ suppose que la valeur est requis, donc si vous transmettez une valeur vide - Aucune ou vide string ("") - clean () lève une exception ValidationError:

Si vous souhaitez qu'une case à cocher ou toute autre valeur soit facultative, vous devez passer required=False au constructeur de champ de formulaire. 

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

Dans les formulaires Django, les champs booléens doivent être créés avec required=False.

Lorsque la case à cocher n'est pas cochée, les navigateurs n'envoient pas le champ dans les paramètres POST des demandes. Sans spécifier que le champ est facultatif, Django le traitera comme un champ manquant s’il n’est pas dans les paramètres POST.

À mon humble avis, ce serait bien pour Django d’avoir ce comportement par défaut pour les champs de formulaire booléens.

(ceci a déjà été répondu dans les commentaires de Yuji mais ce serait utile comme réponse)

30
yairchu

Selon documents officiels :

Si vous souhaitez inclure un booléen dans votre formulaire qui peut être soit True ou False (par exemple une case à cocher cochée ou non cochée), vous devez vous rappeler de passer required=False lors de la création de BooleanField.

11
user

Si vous faites ce qui est suggéré, votre champ up_to_date sera probablement toujours False. Et vous ne saurez jamais si c'est parce que l'utilisateur le veut ainsi, ou parce que l'utilisateur ignoré le champ!

J'ai le même problème avec une question OPT_IN légalement requise. Où, lors de l'inscription, je dois demander à l'utilisateur de prendre une décision de conscience autorisant mon système à lui envoyer des courriers électroniques non sollicités (ou non). Je travaille avec Django-Allauth et une base de données d'utilisateurs personnalisée. 

Je vais partager ma solution ci-dessous dans l'espoir que vous puissiez l'adapter à votre situation - je l'ai adaptée à partir de le 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()
0
Richard Cooke

Assurez-vous de ne pas écraser le formulaire initial lors du premier chargement de la page.

Au départ, je ne vérifiais pas s’il y avait réellement quelque chose à demander. La première fois que la page charge request.GET, la valeur est None et l'appel de SomeForm (request.GET) effacerait les valeurs initiales.

def some_view(request):

    form = SomeForm()

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

Ajout d'une vérification pour s'assurer qu'il y a quelque chose dans request.GET résolu ce problème.

def some_view(request):

    form = SomeForm()

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