Form.is_valid() always true and I can't find any error...

I am stuck with an issue.
I always has form.is_valid() being False and I don’t see what could be the validation errors…
I’ve simplified as much as I could the form…

Here is the code:

models.py:

def default_due_date():
    return datetime.date.today() + timedelta(days=30)    
class Invoice(models.Model):
    number = models.CharField('Numéro de facture', max_length=10, null=True, blank=True)
    label = models.CharField('libellé de facture', max_length=200, null=True,blank = True)
    issuance_date = models.DateField('Date de facture', default=datetime.date.today)
    due_date = models.DateField('Date d\'échéance', default=default_due_date)
    PREVISION = 'PR'
    FACTURE = 'FA'
    AVOIR = 'AV'
    INVOICE_TYPE_CHOICES = [
        (PREVISION, 'Echéance'),
        (FACTURE, 'Facture'),
        (AVOIR, 'Avoir')
        ]
    type = models.CharField('Type de facture',max_length=2, 
        choices=INVOICE_TYPE_CHOICES, default=PREVISION)
    amount = models.DecimalField('Montant',max_digits=11, decimal_places=2, default=0)
    is_issued = models.BooleanField('Emise', default=False)
    is_paid = models.BooleanField('Réglée', default=False)
    payment_date = models.DateField('Date de règlement', null=True,blank=True) 
    business = models.ForeignKey(Business, on_delete=models.CASCADE, verbose_name='Commande')
    market = models.ForeignKey(Market, on_delete=models.RESTRICT, null=True,blank=True)
    purchase_order = models.ForeignKey(Purchase_order, on_delete=models.RESTRICT, null=True,blank=True) 
    contact = models.ForeignKey(Contact, related_name = 'set_invoice_contact', on_delete=models.SET_NULL, \
        null=True, blank=True, verbose_name='Suivi par')   
    addressee = models.ForeignKey(Contact, related_name="set_invoice_adressee" ,on_delete=models.SET_NULL, \
        null=True, blank=True, verbose_name='Destinataire de la facture')
    address =models.ForeignKey(Address, on_delete=models.SET_NULL, \
        null=True, blank=True, verbose_name='Adresse de facturation')

    def set_default_values(self):
        business = Business.objects.get(pk= self.business.id)
        self.address = business.default_address
        self.addressee = business.default_addressee
        self.contact = business.default_contact
        
    def save(self,*args, **kwargs):
        if not self.pk: #si l'échéance est nouvelle
            self.set_default_values() #on initialise les valeurs de "contact" par défaut
        super().save(*args,**kwargs)
        
    class Meta:
        ordering = ['issuance_date']
        verbose_name = 'Facture'
    
    def __str__(self):
        return self.label
    
    def get_absolute_url(self):
        return reverse('invoice_detail', args=[str(self.id)]) 

forms.py

class BusinessForm(ModelForm):
    def clean(self):
        cleaned_data = super().clean()
        name = cleaned_data.get('name')
        account_exec = cleaned_data.get('account_exec')
        amount = cleaned_data.get('amount')
        ponderation = cleaned_data.get('ponderation')
        contract_type = cleaned_data.get('contract_type')
        if name or account_exec or amount :
            if not (name and account_exec and amount and ponderation):
                msg = 'Si un des champs d\'affaire est saisi, tous les champs doivent l\'être'
                self.add_error('name', msg)
            
    def init_amount_pondered(self):
        if self.instance.pk:
            # si le business existe, on affiche le montant pondéré
            amount = self.instance.amount_pondered()
        else:
            # si le business n'existe pas (pas encore créé), on affiche 0
            amount = 0
        return amount

    def init_amount_to_plan(self):
        if self.instance.pk:
            amount = self.instance.amount_to_plan()
        else:
            amount = 0
        return amount
    
    amount_pondered = forms.CharField(disabled=True)
    amount_to_plan = forms.CharField(disabled = True)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # On initialise les montants calculés (montant pondéré et montant restant à planifier)
        self.fields['amount_to_plan'].initial = self.init_amount_to_plan()
        self.fields['amount_pondered'].initial = self.init_amount_pondered()

    class Meta:
        model = Business
        fields = ['name', 'customer', 'account_exec', 'amount','ponderation','contract_type']
        widgets = {'creation_date': DatePickerInput,'signature_date': DatePickerInput}

class BusinessDetailForm(BusinessForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # On initialise les montants calculés (montant pondéré et montant restant à planifier)
        self.fields['amount_to_plan'].initial = self.init_amount_to_plan()
        self.fields['amount_pondered'].initial = self.init_amount_pondered()
        # On restreint la liste de choix de contacts à ceux qui sont rattachés au client en cours
        self.fields['default_contact'] = forms.ModelChoiceField(\
            queryset=Contact.objects.filter(customer=self.instance.customer))
        # On restreint la liste de choix de contact à ceux qui sont rattachés au client en cours
        self.fields['default_addressee'] = forms.ModelChoiceField(\
            queryset=Contact.objects.filter(customer=self.instance.customer))
        # On restreint la liste de choix de adresses à celles qui sont rattachées au client en cours
        self.fields['default_address'] = forms.ModelChoiceField(\
            queryset=Address.objects.filter(customer=self.instance.customer))
        
    class Meta:
        model = Business
        fields = ['name', 'customer', 'customer_ref','account_exec', 'amount','ponderation','contract_type',
                  'default_contact','default_addressee','default_address']
        widgets = {'creation_date': DatePickerInput,'signature_date': DatePickerInput}

class InvoiceForm(ModelForm):
    def delete_invoice(self):
        instance = self.instance
        if instance.pk:
            instance.delete()
        return
            
    def clean(self):
        cleaned_data = super().clean()
        label = cleaned_data.get('label')
        issuance_date = cleaned_data.get('issuance_date')
        due_date = cleaned_data.get('due_date')
        amount = cleaned_data.get('amount')
        if due_date < issuance_date:
            msg = 'la date d\échéance ne peut pas être antérieure à la date s\'émission.'
            self.add_error('due_date', msg)
        if label or amount !=0 :
            if not (label and amount!=0):
                msg = 'Si un des champs d\'affaire est saisi, tous les champs doivent l\'être'
                self.add_error('label', msg)
        return

    error_css_class = 'error'
    required_css_class = 'required'
    number = forms.CharField(disabled=True, required=False, label='N° de facture')
    is_issued = forms.BooleanField(required = False, label='Emise')
    is_paid = forms.BooleanField(required = False, label='Réglée')
      
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['number'].initial = self.instance.number
        if self.fields['is_issued'] == True:
            self.issuance_date = forms.DateField(disabled=True)
  
    class Meta:
        model = Invoice
        #fields= ['business','label','is_issued','issuance_date','due_date','type','amount', 'is_paid','payment_date']
        fields= ['business','label','is_issued','type','amount', 'is_paid',]

class InvoiceDetailForm(InvoiceForm):
    business = forms.CharField(disabled=True)
    payment_date = forms.CharField(required=False)
    
    class Meta:
        model = Invoice
        fields= ['label','amount']

views.py:

class InvoiceDetailView(generic.DetailView):
    model = Invoice
    fields = '__all__'
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        invoice = get_object_or_404(Invoice, pk=self.kwargs['pk'])
        business = get_object_or_404(Business, pk=invoice.business.id)
        context['invoice_form'] = InvoiceDetailForm(instance=invoice)
        context['business_form'] = BusinessDetailForm(instance = business)
        return context

    def post(self, request, *args, **kwargs):
        pk = self.kwargs['pk'] 
        invoice = get_object_or_404(Invoice, pk=pk) 
        business = get_object_or_404(Business, pk=invoice.business.id) 
        business_form = BusinessDetailForm(instance = business)
        invoice_form = InvoiceDetailForm(instance = invoice, prefix = 'invoice')
        if 'update' in request.POST:                 
            messages.warning(request, request.POST)
            if invoice_form.is_valid():
                invoice_form.save()
                messages.success(request, 'Les données de Facture ont été mises à jour.')
            else:
                messages.warning(request, 'les données de Facture sont incorrectes.'+ str(invoice_form.errors))

      
        return render(request, 'gestcom/invoice_detail.html',\
                {'invoice_form':invoice_form, 'business_form':business_form}) 

HTML

{% extends "base_generic.html" %}

{% block content %}

<form method="post">
  {% csrf_token %}
  <div>
    <h2>
    <span>Facture n°: </span>
    {% if invoice.number%}
      <span> {{invoice.number}}</span>
    {% else %}
      <span> En attente</span>
    {%endif%}
  </h2>
  </div>
  <div>
    <table>
    </tr>
      <th>Montant de l'échéance</th>
      <td>{{invoice_form.amount}}</td>    
    </tr>
    <tr>
      <th>{{invoice_form.is_issued.label}}</th>
      <td>{{invoice_form.is_issued}}</td>
      <th>{{invoice_form.is_paid.label}}</th>
      <td>{{invoice_form.is_paid}}</td>
    </table>
    <br>
    <div class="container">
      <div class="row">
        <div class="col-lg-4">
          <table>
            <tr>
                <th>{{business_form.customer_ref.label}}</th>
                <td>{{business_form.customer_ref}}</td>
            </tr>
           </table>
        </div>
      </div>
    </div>
  </div>

    <div>
        <button id = "update" name="update" type="submit" class="btn btn-primary">
        <i class='bx bx-save nav_icon'></i>
        Sauvegarder
        </button>
    </div>
</form>
<div>
<strong>Erreurs de champ:</strong>
{% if form.errors %}
<div class="alert alert-danger">
    <ul>
        {% for field_errors in form.errors.values %}
            {% for error in field_errors %}
                <li>{{ error }}</li>
            {% endfor %}
        {% endfor %}
    </ul>
</div>
{% endif %}
</div>
<div>
<strong>Erreurs "non form":</strong>
{% if form.non_field_errors %}
    <div class="alert alert-danger">
        <ul>
            {% for error in form.non_field_errors %}
                <li>{{ error }}</li>
            {% endfor %}
        </ul>
    </div>
{% endif %}
</div>

<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>

<script type="text/javascript">
$(document).ready(function(){
  $("#update").click(function(){
    if ($("input[name*='DELETE']:checked").length > 0) {
      if(confirm("Confirmez-vous vouloir supprimer les lignes sélectionnées?")){
        // Récupérer l'URL dynamique en fonction de l'ID particulier
        var url = "/gestcom/test/" + $("#themeId").val();
        // Le code pour envoyer la requête POST avec l'URL dynamique
        $.ajax({
          type: "POST",

        });        
      } else {
        return false
      }
    } 
  });
});
</script>
{% endblock %}

What error messages are you getting in the form?
(See Form.errors and How errors are displayed)

I do notice that you’re not running is_valid on business_form, nor do I see where you’re saving it.

I get not message at all, even I do display them in the template. Here is a screen shot of what I get when I submit the form (with no data changed).

As you can see, I get the message "les données de Facture sont incorrectes. which is in the “else” statement of my test on invoice_form.is_valid() and no error message is displayed on the bottom of the screen…

Regarding the business, the work is still in progress…

In addition, I’ve just tried to remove this test on form.is_valid() and I get the error hereafter
Maybe it can help?
.

One issue I see is that when you’re creating the form for the get, you have:

but when you bind the submitted data to the form you have:

This raises two issues:

  • You’re not defining the prefix when you’re creating the form for the get.

  • You’re not binding the post data to the forms in the post.