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

Django inlineformset标记克隆行删除及保存异常、校验行丢失问题求解

具体修改方案

1. 视图(views.py)修改

两处inlineformset_factory定义都将can_delete改为True,启用Django原生删除字段能力:

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=True # 此处修改
)

原有保存逻辑无需调整,formset会自动过滤标记为删除的行。


2. HTML模板修改

2.1 初始行渲染逻辑调整

新增form-row类标记每一行、添加原生DELETE隐藏字段、将删除按钮的id改为class避免重复:

{{di_form.management_form}}
<div id = "rowAddition">
{% for form in di_form %}
<div class="form-row">
 <div class="row">
  <div class="col-md-2">
   <div class="form-group">
    <label for="{{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="{{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="{{form.lastportid.id_for_label}}">Last Port ID <span class="text-danger">*</span></label>
    {{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 removerow" {% if forloop.first and di_form.total_form_count == 1 %}style="display: none;"{% endif %}>
      <i class="dripicons-minus"></i>
     </button>
     {{form.DELETE}}
    </div>
   </div>
  </div> 
 </div>
 {{form.id}}
</div>
{%endfor%}

<!-- 空表单移到外部避免被遍历 -->
<div id="empty-form" class="form-row" style="display: none;">
 <div class="row">
  <div class="col-md-2">
   <div class="form-group">
    <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>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>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 removerow">
       <i class="dripicons-minus"></i>
      </button>
      {{di_form.empty_form.DELETE}}
    </div>
   </div>
  </div>
 </div>
 {{di_form.empty_form.id}}
</div>
<div id='morerows'></div>
</div>

注意:所有表单输入框如果要触发修改确认逻辑,请添加onchange="portidChange(this.value)"属性


3. JS代码修改

修复表单前缀错误、新增索引重编逻辑、处理校验回显初始化:

const maxrow = parseInt($('#id_deviceinterface_set-MAX_NUM_FORMS').val());
const formPrefix = 'deviceinterface_set';
let changeFlag=0;

// 公共函数:重新编号所有行索引,保证连续性
function renumberFormRows() {
    $('.form-row').each(function(index) {
        $(this).find('input, select, textarea, label').each(function() {
            const oldName = $(this).attr('name');
            const oldId = $(this).attr('for') || $(this).attr('id');
            if (oldName) {
                const newName = oldName.replace(new RegExp(`${formPrefix}-\\d+-`), `${formPrefix}-${index}-`);
                $(this).attr('name', newName);
            }
            if (oldId) {
                const newId = oldId.replace(new RegExp(`${formPrefix}-\\d+-`), `${formPrefix}-${index}-`);
                $(this).attr('for') ? $(this).attr('for', newId) : $(this).attr('id', newId);
            }
        });
    });
    // 更新总表单数和按钮状态
    const total = $('.form-row').length;
    $('#id_' + formPrefix + '-TOTAL_FORMS').val(total);
    if (total >= maxrow) {
        $('#addrow').attr("disabled", true).attr("class","btn btn-rounded btn-danger").html("Reached max limit");
    } else {
        $('#addrow').attr("disabled", false).attr("class","btn btn-outline-success btn-rounded").html("<i class='dripicons-plus'></i>");
    }
    total == 1 ? $('.removerow').hide() : $('.removerow').show();
}

// 页面加载时初始化,解决校验回显问题
$(document).ready(function() {
    renumberFormRows();
    $('#deleteConfirmation').modal('hide');
});

function portidChange(val) {
    changeFlag = val ? 1 : 0;
}

// 新增行逻辑
$('#addrow').click(function () {      
    const totalForms = parseInt($('#id_' + formPrefix + '-TOTAL_FORMS').val());
    const newRow = $('#empty-form').html().replace(/__prefix__/g, totalForms);
    $('#morerows').append(newRow);
    renumberFormRows();
    changeFlag=0;
});

// 删除行逻辑
$(document).on('click', '.removerow', function () {
    const $row = $(this).closest('.form-row');
    if(changeFlag==1) {
        $('#removerowConfirmed').off('click').on('click', function () {
            // 标记删除,不直接删DOM,Django会自动过滤
            $row.find('input[name$="-DELETE"]').prop('checked', true);
            $row.hide();
            $('#deleteConfirmation').modal('hide');
            renumberFormRows();
        });
        $('#deleteConfirmation').modal('show');
    } else {
        $row.find('input[name$="-DELETE"]').prop('checked', true);
        $row.hide();
        renumberFormRows();
    }         
});

内容的提问来源于stack exchange,提问作者Calvin Lin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 23:09:03