Is there a way to control the formset properly?

I have a web which has the following form as shown in the pic below. Its basically a formset which is control by a button to limit the no. of form created or remove whatever form that is not needed. So whenever i clone the formset. It will appear as form-(number)-(fieldname). I have a ‘-’ button which has the codes to reduce the number by 1.

$(document).on('click', '#removerow', function () {
        let totalForms = $('#id_form-TOTAL_FORMS').val();
        let actualformcount = totalForms-1;
        $('#id_form-TOTAL_FORMS').attr('value', (parseInt (totalForms))-1);
            $(this).closest('#morerows .row').remove();
 

            if(actualformcount < maxrow){
                $('#addrow').attr("disabled", false)
                $('#addrow').attr("class","btn btn-outline-success btn-rounded")
                $('#addrow i').attr("class","dripicons-plus")
                $('#addrow').html("+")
            } 

The above codes work for this image:
Working as intended for delete of last row

More about this 1st image: If i clone to the max limit i set and i delete the last row (circle in red). The ones that are circle in blue get saved to my database.

But if i change the way i delete. I delete the 2nd clone (circle in blue this time), the ones that are circle in red this time get saved to database. But the last row does not get saved: Not working as intended for delete of any row other than last row

Can anyone advise me how to correct this to make it still save all the rows that are left in the form except for the deleted one?

views.py

  def device_add(request):
    if request.method == "POST":
        device_frm = DeviceForm(request.POST) 
         dd_form = DeviceDetailForm(request.POST)
        
        di_formset = inlineformset_factory(Device, DeviceInterface, fields=('moduletype', 'firstportid', 'lastportid'),widgets={  'firstportid':TextInput(attrs={'placeholder': 'e.g. TenGigabitEthernet1/0/1'}), 'lastportid':TextInput(attrs={'placeholder':'eg. TenGigabitEthernet1/0/48'})},extra=1,max_num=3, can_delete=False)
        di_form=di_formset(request.POST)
        if device_frm.is_valid():
        # Create and save the device
        # new_device here is the newly created Device object
            new_device = device_frm.save()
            if dd_form.is_valid():
                # Create an unsaved instance of device detail
                deviceD = dd_form.save(commit=False)
                # Set the device we just created above as this device detail's device
                deviceD.DD2DKEY = new_device              
                deviceD.save()
                if di_form.is_valid():
                    deviceI=di_form.save(commit=False) 
                    print(deviceI)
                    for instances in deviceI:          
                        instances.I2DKEY=new_device
                        instances.save()
                    return render(request, 'interface/device_added.html',{'devices':Device.objects.all()})
                return render(request,'interface/device_add.html',{'form':device_frm, 'dd_form': dd_form, 'di_form':di_form})
            return render(request,'interface/device_add.html',{'form':device_frm, 'dd_form': dd_form, 'di_form':di_form})
        return render(request,'interface/device_add.html',{'form':device_frm, 'dd_form': dd_form, 'di_form':di_form})
    else:
        device_frm = DeviceForm()
        dd_form = DeviceDetailForm()
        di_formset = inlineformset_factory(Device, DeviceInterface, fields=('moduletype', 'firstportid', 'lastportid'), widgets={  'firstportid':TextInput(attrs={'placeholder': 'e.g. TenGigabitEthernet1/0/1'}), 'lastportid':TextInput(attrs={'placeholder':'eg. TenGigabitEthernet1/0/48'})},extra=1, max_num=3, can_delete=False)
        di_form=di_formset(queryset = DeviceInterface.objects.none())
        return render(request,'interface/device_add.html',{'form':device_frm, 'dd_form': dd_form, 'di_form':di_form})

html

       {{di_form.management_form}}
       <div id = "rowAddition">
       {% for form in di_form %}
       <div>
        <div class="row">
         <div class="col-md-2">
          <div class="form-group">
           <label for="{{di_form.moduletype.id_for_label}}">Module Type<span
                                            class="text-danger">*</span></label>
            {{form.moduletype}}               
          </div>
         </div>
         <div class="col-md-4">
          <div class="form-group">
           <label for="{{di_form.firstportid.id_for_label}}">First Port ID<span
                                            class="text-danger">*</span></label>
            {{form.firstportid}}
          </div>
         </div>
         <div class="col-md-4">
          <div class="form-group">
           <label for="{{di_form.lastportid.id_for_label}}">Last Port ID <span
                                            class="text-danger">*</span></label>
            {{form.lastportid}}
          </div>
         </div>  
        </div>
       </div>
       {%endfor%}

       <div id="empty-form" style="display: none;">
        <div class="row">
         <div class="col-md-2">
          <div class="form-group">
           <label for="{{dd_form.moduletype.id_for_label}}">Module Type<span
                                            class="text-danger">*</span></label>
            {{di_form.empty_form.moduletype}}               
          </div>
         </div>
         <div class="col-md-4">
          <div class="form-group">
           <label for="{{di_form.firstportid.id_for_label}}">First Port ID<span
                                            class="text-danger">*</span></label>
            {{di_form.empty_form.firstportid}}
          </div>
         </div>
         <div class="col-md-4">
          <div class="form-group">
           <label for="{{di_form.lastportid.id_for_label}}">Last Port ID <span
                                            class="text-danger">*</span></label>
            {{di_form.empty_form.lastportid}}
          </div>
         </div>                                
         <div class="col-md-1">
          <div class="form-group">
           <div class="text-sm-center">
            <br />
             <button type="button" class="btn btn-outline-danger btn-rounded"
              id="removerow" style="display: none;" data-toggle="modal"><i class="dripicons- 
              minus" ></i></button>
           </div>
          </div>
         </div>
         <!--more rows-->
         <div id='morerows'></div>

Script

let changeFlag=0;
let maxrow = $('#id_form-MAX_NUM_FORMS').attr('value');
let totalForms = $('#id_form-TOTAL_FORMS').val();
console.log(maxrow)
console.log(totalForms)
$('#deleteConfirmation').modal('hide');
function portidChange(val) {
    changeFlag = 1;
    if (val =="")
    changeFlag = 0;
    
}
$('#addrow').click(function () {      
    let totalForms = $('#id_form-TOTAL_FORMS').val();
    console.log(totalForms)        
    $('#empty-form #removerow').css('display','block');       
    let wholerowclone = $('#empty-form').clone();        
    $('#morerows').append(wholerowclone.html().replace(/__prefix__/g, totalForms));
    $('#id_form-TOTAL_FORMS').attr('value', (parseInt (totalForms))+1);  
    changeFlag=0;    
    if(totalForms==maxrow) {            
        $('#addrow').attr("disabled", true);
        $('#addrow').attr("class","btn btn-rounded btn-danger");
        $('#addrow i').attr("class","");
        $('#addrow').html("Reached max limit");
    }      
})
$(document).on('click', '#removerow', function () {
    let totalForms = $('#id_form-TOTAL_FORMS').val();
    console.log(totalForms + " total forms");
    let actualformcount = totalForms-1;        
    if(changeFlag==1) {
        console.log("changeFlag=1");
        $('#removerow').attr('data-target', '#deleteConfirmation')           
        $('#deleteConfirmation').modal('show');
        $('#removerowConfirmed').click(function () {
            console.log("clicked")
            $('#removerow').closest('#morerows.row').remove();
            $('#deleteConfirmation').modal('hide');
            if(actualformcount < maxrow) {
                $('#addrow').attr("disabled", false)
                $('#addrow').attr("class","btn btn-outline-success btn-rounded")
                $('#addrow i').attr("class","dripicons-plus")
                $('#addrow').html("+") 
            }  
        })
    }
    else {
        console.log("ChangeFlag 0");
        $('#id_form-TOTAL_FORMS').attr('value', (parseInt (totalForms))-1);
        $(this).closest('#morerows .row').remove();
        if(actualformcount < maxrow){
            $('#addrow').attr("disabled", false)
            $('#addrow').attr("class","btn btn-outline-success btn-rounded")
            $('#addrow i').attr("class","dripicons-plus")
            $('#addrow').html("+")
        } 
    }         
});

One of the things you don’t want to do is actually remove a form from a formset.

In the docs for Understanding the ManagementForm, there’s this section:

On the other hand, if you are using JavaScript to allow deletion of existing objects, then you need to ensure the ones being removed are properly marked for deletion by including form-#-DELETE in the POST data. It is expected that all forms are present in the POST data regardless.

(emphasis added)

Also see the section on can_delete.

Thanks for the suggestion but is there any way to make it work by using what I have right now? Because now the issue is assuming i clone the form twice. SO i have form1(fieldname), form2(fieldname). If i delete the 1st one. Form2 still stay as form2 instead of changing to form1

What you have now is contrary to how formsets are designed and intended to work. You’re going to find that it’s a lot easier and going to create far fewer problems if you work with Django rather than trying to work around it.
(It really is a case of you being better off changing how you think about this than trying to change Django to make it work the way you think it should work. As I frequently say, “Don’t fight the framework”.)

The intended behavior is that Form 2 stays as form2.
When the form is “deleted”, it’s still passed back to Django. It’s up to the view to decide what having the “deleted” flag set means.

If you’ve constructed the form as a model formset, it might mean that you want to delete that object. If it’s an inline formset, it might mean you want to delete that object, or it may mean you just want to remove the relationship. (Or, you could make it mean whatever you want it to mean.)

Oh okay. I get what u are saying. But why is the form not saving as intended? It work well if i delete the last cloned row everytime. But it just doesnt work when its the row deleted is not the last row. Shouldnt the other forms(number)(field) remaining be saved?

For inline formset, i get what you are trying to tell me. Correct me if im wrong, for the current page, if i use modalformset to submit data, I can use inline to retrieve the data in another page which has separate view? From what i understand, both formset are quite similar.

Because you’re deleting the row in the form. Everything after that deleted row is probably lost because of that. (That’s just a guess. I’m not really familiar with all the ways that a formset can fail.)

Hmmm… probably. I’ve never used them that way though.

An inline formset is a model formset, with the additional functionality of managing a relationship between the multiple object in the model formset and an instance of a “base” model.

Typically, I use the inline formset on the same page as the parent object. What an inline formset does is manage that association between the base object and all the objects in that related inline formset.

So from what I’ve seen of what you’ve posted, you want to pretty much throw away 50% of your formset handling, and build your page using the inline formset instead of a model formset.

Oh okay. Thanks for the explanation. The reason why i was using modal was because these codes were sent to me to work on it and under views, the import was importing modalformset.

From what i seen in youtube guides (Not many available videos on it), inline has the delete button. Is it possible to remove it? Because i dont need that button function as i have a delete page that does the delete. I only need to control the no. of formset cloned

I believe that if you set the can_delete attribute to False, no delete button is generated.

Thanks ! But after changing from modal to inline my id_form-MAX-NUM- FORMS and id_form-TOTAL-FORMS becomes undefined. How come? I thought this 2 can be called regardless of using modal/inline? I have set the max in my inline to 3 currently

What are you seeing in the management form in the rendered formset?

Currently in my web, i see the following.
The original(Without the - button) : deviceinterface_set-0-(fieldname)
The cloned one : deviceinterface_set-undefined-(fieldname)
Somehow the cloning is also limited to once at the moment.

Those items aren’t part of the management form.

Maybe it would be better at this point to have you repost the view and templates for this.

Sorry about that. Heres the management part. This is view in the tools of google chrome. image
I have edit the post with my view, template and script for the problem

I got it to work currently. I change all the id_form to id_deviceinterface_set. But it is still the same as output as i was using modalformset though. PS: I cant seem to edit my post anymore as the edit button button is gone.

I tried every combination. Is my logic in the script wrong? It seem to work for every other combination except for 3 of the combination.
These are the one that fail: When i have 3 rows. And i delete the middle row. It saves the 1st row only. 3rd row data is lost. When i have 4 row, i delete the 2nd row. It saves the 1st and the 3rd row. 4th row lost. If i delete the 3rd row. It saves 1st and 2nd row but 4th row lost.

Update: Under the management form, whenever i delete a row. Its counting properly for the total forms. When +, the value increase by 1. When -, the value decrease by 1. But when saved its lost for the above combination. If its just removing the last row. Everything get saved properly

As stated earlier, you do not want to actually delete rows. You want to mark them as deleted and allow Django on the back end to “see” them.

I got a question regarding this formset issue. I have removed the + and - buttons that are controlling the number of formset. So at 1st load, the page just straight away show the formset with the amount of form i declared in extra. How do i marked them as delete to throw the data away?

Here’s my following code. I have 3 forms for the formset. 0 = Form 1, 1 = Form 2, 2 = Form 3. A example for the 2nd if, i want to reject the form 3 but I have no idea how to write my code to remove it. I tried to put in the 2nd if, deviceI.delete(2), it says list have no .delete()

for instances in deviceI
  if FPFV0 == LPFV0 and FPMV0==LPMV0 and FPFV1 == LPFV1 and FPMV1==LPMV1 and FPFV2 == LPFV2 and FPMV2==LPMV2:         
    instances.I2DKEY=new_device
    instances.save()
  if FPFV0 == LPFV0 and FPMV0==LPMV0 and FPFV1 == LPFV1 and FPMV1==LPMV1:         
    instances.I2DKEY=new_device
    instances.save()
  if FPFV0 == LPFV0 and FPMV0==LPMV0 and FPFV2 == LPFV2 and FPMV2==LPMV2:         
    instances.I2DKEY=new_device
    instances.save()
  if FPFV1 == LPFV1 and FPMV1==LPMV1 and FPFV2 == LPFV2 and FPMV2==LPMV2:        
    instances.I2DKEY=new_device
    instances.save() 

The has_changed method on a form can identify forms with no data entered. You can also use the changed_data attribute to identify which fields were changed.

Keep in mind that a formset is just a wrapper for a number of similar forms. Each form within a formset is still a form with the complete forms API available to it.

Sorry about that. Maybe my question was not clear but I am looking for a way to not saved the wrongly key in form or if the data is duplicated. Currently what I am intend to do in my code is this. I have 2 fields: firstport and lastport. In these 2 fields, the format has to be int/int/int. As my formset is declared extra=3. There will be 3 rows of this 2 field appearing. So for example my first row will be 1/0/1 for first port and lastport 1/0/3. My 2nd row has 1/0/40 for firstport and 1/0/1 for last port. My 3rd row will have 1/0/1 for firstport and 1/0/3 for lastport. As u can see, my first row and 3rd row has the same thing. So i would like to remove one of the row. For now it its saving both 1st row and 3rd row under different id in the table. And as u can see for 2nd row, my firstport last value is 40 while my lastport last value is 1. I would also like to remove this instance as my firstport value is larger than lastport