You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用JavaScript删除Django Formset额外表单?附现有代码

Solution for Django Formset Delete Functionality

Got it, let's tackle this formset deletion issue step by step. First, I notice you've already got the add functionality working, so we'll build on that to add both automatic deletion based on the visitor count dropdown and manual delete buttons for individual forms.

1. First, Configure Your Formset in the Backend

To make sure Django properly handles form deletion (even if we're removing form blocks via JS), update your formset definition to include can_delete=True (this adds a hidden DELETE field to each form, which helps Django track deletions if needed):

from django.forms import formset_factory

# Replace VisitorForm with your actual form class
VisitorFormSet = formset_factory(VisitorForm, extra=1, can_delete=True)

2. Update Your Template Structure

We need to wrap each form in the formset with a container class (to target them easily) and add a delete button to each form block:

<div id="userform">
    {% for form_data in formset %}
    <!-- Add a container class for each form block -->
    <div class="visitor-form mb-3">
        {{ form_data.management_form }}
        {{form_data.first_name|as_crispy_field}}
        {{form_data.last_name|as_crispy_field}}
        {{form_data.middle_name|as_crispy_field}}
        {{form_data.date_of_birth|as_crispy_field}}
        {{form_data.gender|as_crispy_field}}
        {{form_data.passport|as_crispy_field}}
        {{form_data.passport_ex|as_crispy_field}}
        {{form_data.nationality|as_crispy_field}}
        <!-- Add delete button -->
        <button type="button" class="btn btn-danger delete-form">Delete This Visitor</button>
    </div>
    {% endfor %}
</div>

3. Update Your JavaScript Code

We'll modify your existing JS to handle deletion, fix form indexing after deletion, and sync the visitor count dropdown.

Full Updated JavaScript:

$(document).ready(function(){
    $('.menu').hide("slide");
    $('#show').click(function() {
        $('.menu').toggle("slide");
    });
    $('#hide').click(function() {
        $('.menu').hide("slide");
    });

    // Handle visitor count dropdown change
    $('#id_number_of_vistor').change(()=>{
        var selectedCount = parseInt($('#id_number_of_vistor option:selected').text());
        var totalForms = parseInt($('#id_Visitor-TOTAL_FORMS').val());
        
        if (selectedCount > totalForms) {
            // Add missing forms
            for (let i = totalForms; i < selectedCount; i++) {
                cloneMore('#userform .visitor-form:last','Visitor');
            }
        } else if (selectedCount < totalForms) {
            // Remove extra forms from the end
            let formsToRemove = totalForms - selectedCount;
            $('.visitor-form').slice(-formsToRemove).remove();
            // Reindex remaining forms and update total
            reindexForms('Visitor');
        }
    });

    // Handle manual delete button clicks
    $(document).on('click', '.delete-form', function() {
        var formBlock = $(this).closest('.visitor-form');
        // Ensure we keep at least one form
        if ($('.visitor-form').length > 1) {
            formBlock.remove();
            reindexForms('Visitor');
            // Sync the dropdown with the remaining form count
            $('#id_number_of_vistor').val($('.visitor-form').length);
        } else {
            alert("You need at least one visitor form!");
        }
    });
});

// Updated cloneMore function to target the last form and add the container class
function cloneMore(selector, type) {
    var newElement = $(selector).clone(true);
    // Reset all input values
    newElement.find(':input').each(function() {
        var total = $('#id_' + type + '-TOTAL_FORMS').val();
        var name = $(this).attr('name').replace(/-\d+-/, '-' + total + '-');
        var id = 'id_' + name;
        $(this).attr({'name': name, 'id': id}).val('').removeAttr('checked');
    });
    // Update label for attributes
    newElement.find('label').each(function() {
        var total = $('#id_' + type + '-TOTAL_FORMS').val();
        var newFor = $(this).attr('for').replace(/-\d+-/, '-' + total + '-');
        $(this).attr('for', newFor);
    });
    // Increment total forms count
    var total = parseInt($('#id_' + type + '-TOTAL_FORMS').val()) + 1;
    $('#id_' + type + '-TOTAL_FORMS').val(total);
    // Append new form after the last one
    $(selector).after(newElement);
}

// Reindex forms after deletion to fix name/id attributes
function reindexForms(type) {
    var forms = $('.visitor-form');
    $('#id_' + type + '-TOTAL_FORMS').val(forms.length);
    
    forms.each(function(index) {
        // Update all input names and IDs
        $(this).find(':input').each(function() {
            var name = $(this).attr('name').replace(/-\d+-/, '-' + index + '-');
            var id = 'id_' + name;
            $(this).attr({'name': name, 'id': id});
        });
        // Update all label for attributes
        $(this).find('label').each(function() {
            var newFor = $(this).attr('for').replace(/-\d+-/, '-' + index + '-');
            $(this).attr('for', newFor);
        });
    });
}

Key Changes Explained:

  • visitor-form Class: Wraps each form block so we can easily target and manipulate individual forms.
  • reindexForms Function: Fixes the name and id attributes of remaining forms after deletion, which is critical for Django to process the formset correctly (no broken index gaps).
  • Dropdown Sync: When you delete a form manually, the visitor count dropdown updates to match the current number of forms, and vice versa.
  • Minimum Form Check: Ensures users can't delete the last remaining form (adjust this if your use case allows zero forms).

This should fully implement both automatic and manual deletion functionality for your formset.

内容的提问来源于stack exchange,提问作者Mhadi Ahmed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 06:38:02