Adding a form with JS problem!

Hi everyone,

0

I am facing a problem when adding dynamically a new form in a table with JS. The formset is generated. I can add a new line, but when I submit my formset, the last line is not detected, the error gives me :

Formset errors sont: [{}, {}, {}, {‘num’: [‘This field is required.’], ‘description’: [‘This field is required.’]}]

Do you have any clues what should be the problem? :frowning:

Thanks a lot! Regards models.py

    class Link1(models.Model):
        num=models.IntegerField()
        description=models.CharField(max_length=100)


    class Case(models.Model):
    link=models.ManyToManyField(Link1,related_name='cases')



    forms.py

    class Form1(forms.Form):
        num=forms.IntegerField(label=None,widget=forms.Select(choices=liste_1,attrs={'class': 'custom-select text-white','style':'background-color:#063c5c'}))
        description=forms.CharField(label=None,max_length=100, widget=forms.Select(choices=liste_description,attrs={'class': 'custom-select text-white', 'style':'background-color:#063c5c'}))
    views.py

    def validation2(request, case_id):
        case = get_object_or_404(Case, id=case_id)
        case_id2=case.id
        formset1=formset_factory(Form1,extra=0)
        if request.method=="POST":
            myform = formset1(request.POST)
            if myform.is_valid():
                case.link.clear()
                for f in myform:
                    cd=f.cleaned_data
                    print("cd:")
                    print(cd)
                    #On va effacer et réaffecter tout!
                    numero=int(cd['num'])
                    desc=cd['description']
                    obj,boleensiexiste=Link1.objects.get_or_create(num=numero,description=desc)
                    case.link.add(obj)
                    case.save()    
            return redirect(reverse('validation1', kwargs={'case_id': case_id2}))
        else:
            liste_initiales=[]
            for link in case.link.all():
                liste_initiales.append({'num': link.num ,'description':link.description})
            myform=formset1(initial=liste_initiales)
            print(myform)
        context={'myform':myform, 'case':case}
        return render(request,'analyzis.html', context)
html

      <table id="TableRecap" class="table order-list table-borderless text-white p-0 mx-5" style="background-color:#063c5c">
          <thead>
            <tr>
              <th scope="col" ><div class="text-center"><h5></h5></div></th>
              <th scope="col" ><div class="text-center"><h5></h5></div></th>
              <th scope="col"> </th>
          </tr>
        </thead>
        <form action="{% url "validation2" case.id%}" method="post">
        {% csrf_token %}
        {{ myform.management_form }}
        <tbody id="myTableBody">
         {% for formz in myform %}
          <tr><td>
            {{ formz.num }}
          </td><td>
            {{ formz.description}}
          </td><td>
            <img class="btn-validate text-info" src="{% static '/check.png' %}"  width="35" height="35"  alt="validate"/>
            <img class="btn-delete" src="{% static '/x.png' %}" width="30" height="30" alt="delete" />
          </td></tr>
        {% endfor %} 
        </tbody>
        
        <tfoot>
        <tr><td>              
        </td><td>
          <input type="button" class="btn btn-info float-md-right" id="addrow" value="Ajouter un élément" />
        </td><td>
          <input type="submit" class="btn btn-info float-md-left" value="Valider"/>
        </td></tr>
        </tfoot>
        </form>
        </table>

js

```
$("body").on("click", "#addrow", function(){ 
  var compteur=$("#id_form-TOTAL_FORMS").attr("value");
  alert(compteur);
  var num = '<td><select name="form-'+ compteur +'-num" class="form-control text-white text-center" style="background-color:#143244" id="id_form-'+ compteur +'-num"><option value="11" >11</option><option value="12" selected>12</option></select></td>';
  var des = '<td><select name="form-'+ compteur +'-description" class="form-control text-white" style="background-color:#143244" maxlength="100" id="id_form-'+ compteur +'-description"><option value="d">d</option><option value="c" selected>c</option><option value="ab">ab</option></select></td>';
  var ligne = '<tr class="valide">' + num + des ;
  ligne += '<td>'+'<img type="button" src='+ url1 +' width="35" height="35" class="btn-validate" alt="validate" onclick="validate()" />'
  ligne +='<img type="button" class="btn-delete" src='+ url3 +' width="30" height="30" alt="delete" />'+'</td></tr>';
/*   alert(ligne); */
  compteur++;
  $("#myTableBody").append(ligne);
  $("#id_form-TOTAL_FORMS").attr("value",compteur);
  $("#id_form-INITIAL_FORMS").attr("value",compteur);
   alert($("#id_form-TOTAL_FORMS").attr("value"));
}); 

$("body").on("click", ".btn-delete", function(){
  var compteur=$("#id_form-TOTAL_FORMS").attr("value");
  var indice=$(this).parents("tr").index();

  $(this).parents("tr").remove();
  compteur -= 1;
  depart = indice-1;

  $('#myTableBody tr:gt('+depart+')').each(function(){
      a=$(this).find("td:eq(0) select").attr("id");
      $(this).find("td:eq(0) select").attr("id",'id_form-'+ indice +'-num');
      $(this).find("td:eq(0) select").attr("name",'form-'+ indice +'-num');
      $(this).find("td:eq(1) select").attr("id",'id_form-'+ indice +'-description');
      $(this).find("td:eq(1) select").attr("name",'form-'+ indice +'-description');
      b=$(this).find("td:eq(0) select").attr("id");
      indice++;
  });
  $("#id_form-TOTAL_FORMS").attr("value", compteur);
```

Here’s the html I forgot !

html

          <table id="TableRecap" class="table order-list table-borderless text-white p-0 mx-5" style="background-color:#063c5c">
              <thead>
                <tr>
                  <th scope="col" ><div class="text-center"><h5></h5></div></th>
                  <th scope="col" ><div class="text-center"><h5></h5></div></th>
                  <th scope="col"> </th>
              </tr>
            </thead>
            <form action="{% url "validation2" case.id%}" method="post">
            {% csrf_token %}
            {{ myform.management_form }}
            <tbody id="myTableBody">
             {% for formz in myform %}
              <tr><td>
                {{ formz.num }}
              </td><td>
                {{ formz.description}}
              </td><td>
                <img class="btn-validate text-info" src="{% static '/check.png' %}"  width="35" height="35"  alt="validate"/>
                <img class="btn-delete" src="{% static '/x.png' %}" width="30" height="30" alt="delete" />
              </td></tr>
            {% endfor %} 
            </tbody>
            
            <tfoot>
            <tr><td>              
            </td><td>
              <input type="button" class="btn btn-info float-md-right" id="addrow" value="Ajouter un élément" />
            </td><td>
              <input type="submit" class="btn btn-info float-md-left" value="Valider"/>
            </td></tr>
            </tfoot>
            </form>
            </table>

My first quick look at this is that when you’re adding a row, you’re adding one to both the TOTAL_FORMS and the INITIAL_FORMS values which is incorrect. You only want to add one to TOTAL_FORMS. The INITIAL_FORMS variable tracks how many existing rows are displayed in the formset, which means it’s expecting to find that many entries with the id field (or any other pre-existing key fields).

Ken

Hi Ken,

Thanks. I have changed this. However, it seems that the last added row is still not taken into account.

Which is weird is that:
When sending the form, this appears “Description:

  • This field is required.

Also, the ‘selected’ tag that should appear in the selected option is not sent by the form.

I don’t understand why!

Can you run your code, capture the HTML source from the page, then add a row and capture that HTML, and post them both here?

My gut hunch is that there’s something wrong with the extra rows that you’re adding, but I’d need to see the rendered HTML to find out if I’m right.

Also, when you delete rows, you don’t want to try renumbering the row values. That’ll get Django all mixed up with the pre-existing rows. There’s a DELETE property that you set when a row is deleted.

Side note: What we do is use the empty_form attribute to create a blank form that gets included in a hidden div on the page. When we add a row, we copy the blank form and replace the prefix text with the proper value. That ensures that changes to the form are included in the dynamic form creation as well.

Ken

Hi Ken,

Thanks for the tip regarding empty_form, I will try to see how to use it. However in my case, I don’t directly call the form with {{formset}} but I iterate regarding 2 fields with

{{ formz.num }} {{ formz.description}} .

My html before adding a row is :

     <table id="TableRecap" class="table order-list table-borderless text-white text-lg-center" style="background-color:#063c5c">
  <thead>
    <tr>
      <th scope="col" ><div class="text-center"><h5>Col1</h5></div></th>
      <th scope="col" ><div class="text-center"><h5>Col2</h5></div></th>
      <th scope="col"> </th>
  </tr>
</thead>
<form action="/im_Analyzer/case/66/validation" method="post">
<input type="hidden" name="csrfmiddlewaretoken" value="XXX">
<input type="hidden" name="form-TOTAL_FORMS" value="3" id="id_form-TOTAL_FORMS"><input type="hidden" name="form-INITIAL_FORMS" value="3" id="id_form-INITIAL_FORMS"><input type="hidden" name="form-MIN_NUM_FORMS" value="0" id="id_form-MIN_NUM_FORMS"><input type="hidden" name="form-MAX_NUM_FORMS" value="1000" id="id_form-MAX_NUM_FORMS">
<tbody id="myTableBody">
 
  <tr><td>
    <select name="form-0-num" class="form-control" id="id_form-0-num">
11 12 13 14 15 16 17 18 21 22 23 24 25 26 27 28 31 32 33 34 35 36 37 38 41 42 43 44 45 46 47 48 1 2 3
  <tr><td>
    <select name="form-1-num" class="form-control" id="id_form-1-num">
11 12 13 14 15 16 17 18 21 22 23 24 25 26 27 28 31 32 33 34 35 36 37 38 41 42 43 44 45 46 47 48 1 2 3
  <tr><td>
    <select name="form-2-num" class="form-control" id="id_form-2-num">
11 12 13 14 15 16 17 18 21 22 23 24 25 26 27 28 31 32 33 34 35 36 37 38 41 42 43 44 45 46 47 48 1 2 3
</tbody>

<tfoot>
<tr><td>              
</td><td>
  <input type="button" class="btn btn-add btn-info float-md-right" id="addrow" value="Ajouter un élément" />
</td><td>
  <input type="submit" class="btn btn-info float-md-left" value="Valider"/>
</td></tr>
</tfoot>
</form>
</table>

And when I add a row, it turns into:

<table id="TableRecap" class="table order-list table-borderless text-white text-lg-center" style="background-color:#063c5c">
          <thead>
            <tr>
              <th scope="col"><div class="text-center"><h5>Col1</h5></div></th>
              <th scope="col"><div class="text-center"><h5>Col2</h5></div></th>
              <th scope="col"> </th>
          </tr>
        </thead>
        <form action="/im_Analyzer/case/66/validation" method="post"></form>
        <input type="hidden" name="csrfmiddlewaretoken" value="XXX">
        <input type="hidden" name="form-TOTAL_FORMS" value="4" id="id_form-TOTAL_FORMS"><input type="hidden" name="form-INITIAL_FORMS" value="3" id="id_form-INITIAL_FORMS"><input type="hidden" name="form-MIN_NUM_FORMS" value="0" id="id_form-MIN_NUM_FORMS"><input type="hidden" name="form-MAX_NUM_FORMS" value="1000" id="id_form-MAX_NUM_FORMS">
        <tbody id="myTableBody">
         
          <tr><td>
            <select name="form-0-num" class="form-control" id="id_form-0-num">
11 12 13 14 15 16 17 18 21 22 23 24 25 26 27 28 31 32 33 34 35 36 37 38 41 42 43 44 45 46 47 48 1 2 3
          <tr><td>
            <select name="form-1-num" class="form-control" id="id_form-1-num">
11 12 13 14 15 16 17 18 21 22 23 24 25 26 27 28 31 32 33 34 35 36 37 38 41 42 43 44 45 46 47 48 1 2 3
          <tr><td>
            <select name="form-2-num" class="form-control" id="id_form-2-num">
11 12 13 14 15 16 17 18 21 22 23 24 25 26 27 28 31 32 33 34 35 36 37 38 41 42 43 44 45 46 47 48 1 2 3
        <tr><td><select name="form-3-num" class="form-control" id="id_form-3-num"><option value="11">11</option><option value="12" selected="">12</option><option value="13">13</option><option value="14">14</option><option value="15">15</option><option value="16">16</option><option value="17">17</option><option value="18">18</option><option value="21">21</option><option value="22">22</option><option value="23">23</option><option value="24">24</option><option value="25">25</option><option value="26">26</option><option value="27">27</option><option value="28">28</option><option value="31">31</option><option value="32">32</option><option value="33">33</option><option value="34">34</option><option value="35">35</option><option value="36">36</option><option value="37">37</option><option value="38">38</option><option value="41">41</option><option value="42">42</option><option value="43">43</option><option value="44">44</option><option value="45">45</option><option value="46">46</option><option value="47">47</option><option value="48">48</option></select></td><td><select name="form-3-description" class="form-control" maxlength="100" id="id_form-3-description"><option value="1">1</option><option value="2" selected="">2</option><option value="3">3</option></select></td></tr></tbody>
        
        <tfoot>
        <tr><td>              
        </td><td>
          <input type="button" class="btn btn-add btn-info float-md-right" id="addrow" value="Ajouter un élément">
        </td><td>
          <input type="submit" class="btn btn-info float-md-left" value="Valider">
        </td></tr>
        </tfoot>
        
        </table>

Thanks a lot. I will try today to use emptyform!

Regards

Now, I have this:

{‘num’: 11, ‘description’: ‘1’}
{‘num’: 15, ‘description’: ‘1’}
{‘num’: 17, ‘description’: ‘1’}
{}
{}

It detects 2 lines have been added but it does not take into account the information. When I check the html sent in the post request, I see that “selected” does not appear in the form. I don’t understand why since in the html I have chosen an option for each form control!

One thing I noticed - you’ve got the form inside the table. I’m pretty sure that’s not allowed, but I’m not finding a good reference for that statement at the moment. Try reorganizing your template such that the form tag, csrf token, and management form data are above the table tag, and that the close form tag is below the close table tag.

Ken

Ok thanks! I will work on this!!

Thanks a lot it seems to work !!!

I did not know it was a problem, thanks a lot for your help :slight_smile:

Just don’t ask me how or why I know this… :roll_eyes:

Ahah, anyway, thanks a lot !