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

Django单表单多组联动下拉框实现及参数存储问题咨询

问题根因

  • 模板中重复渲染同一套表单字段,所有组的输入框id、name属性完全重复,smart_selects的联动逻辑默认只绑定第一个匹配id的元素,导致后续组联动失效
  • 模型直接将单组束流参数写在主表中,不支持多组参数存储,手动存数组的方案不符合关系数据库设计规范,后续查询维护难度极高

方案实现步骤

1. 重构模型层(采用一对多关联存储多组束流参数)

修改models.py如下:

from django.db import models
from smart_selects.db_fields import ChainedForeignKey


class IonSpecies(models.Model):
    Name = models.CharField(max_length=10)

    def __str__(self):
        return self.Name

class Energys(models.Model):
    Ion_Species = models.ForeignKey(IonSpecies, on_delete=models.CASCADE)
    Name = models.CharField(max_length=50)

    def __str__(self):
        return self.Name

# 主表:束流请求基础信息
class BeamRequest(models.Model):
    #status choices
    SELECT      = 'Select'
    REQUEST     = 'Request'
    TENTATIVE   = 'Tentative'
    ACCEPTED    = 'Accepted'
    CONFIRMED   = 'Confirmed'
    CANCELLED   = 'Cancelled'
    COMPLETED   = 'Completed'
    QUEUED      = 'Queued'

    STATUS_CHOICES = [
        (SELECT, 'Select an option'),
        (REQUEST, 'Request'),
        (TENTATIVE, 'Tentative'),
        (ACCEPTED,  'Accepted'),
        (CONFIRMED, 'Confirmed'),
        (CANCELLED, 'Cancelled'),
        (COMPLETED, 'Completed'),
        (QUEUED, 'Queued'),
    ]

    DIFBEAMS_CHOICES = [tuple([x,x]) for x in range(1,5)]
    SHIFTS_CHOICES = [tuple([x,x]) for x in range(1,17)]

    Project_Code = models.CharField(unique=True, max_length=20, blank = True)
    Pac_Rate = models.CharField(max_length=10, blank = True)
    Partrec_Contact_Name = models.CharField(max_length=50, blank = True)
    Partrec_Contact_Email = models.EmailField(blank = True)
    Previous_Experiment = models.CharField(max_length=200, blank = True)
    Status = models.CharField(max_length=25, choices=STATUS_CHOICES, default='SELECT')
    Project_Title = models.CharField(max_length=100, blank = True)
    Spokesperson_Name = models.CharField(max_length=50, blank = True)
    Spokesperson_Adress = models.CharField(max_length=50, blank = True)
    Spokesperson_Phonenumber = models.CharField(max_length=20, blank = True)
    Spokesperson_Email = models.EmailField(blank = True)
    Collaborator_Name = models.TextField(blank = True)
    Collaborator_Nationality = models.TextField(blank = True)
    Collaborator_Home_Institute = models.TextField(blank = True)
    Different_Beams = models.IntegerField(choices=DIFBEAMS_CHOICES, default=1)
    Start_Date = models.DateTimeField(blank = True, null=True)
    End_Date = models.DateTimeField(blank = True, null=True)
    Requiered_Equipment = models.TextField(blank = True)
    Special_Requirements = models.TextField(blank = True)
    Special_Safety_Procedures = models.TextField(blank = True)
    Lab_Support_Requirements = models.TextField(blank = True)
    Funded = models.TextField(blank = True)
    Summary = models.TextField(blank = True)

    def __str__(self):
        return self.Project_Code

# 子表:单组束流参数,一个请求对应多组
class BeamParameter(models.Model):
    request = models.ForeignKey(BeamRequest, related_name='beams', on_delete=models.CASCADE)
    Hours = models.IntegerField(default=1)
    Ion_Species = models.ForeignKey(IonSpecies, on_delete=models.CASCADE)
    Energy = ChainedForeignKey(Energys, chained_field="Ion_Species", chained_model_field="Ion_Species", show_all=False, auto_choose=True)
    Flux = models.CharField(max_length=50, blank = True, null = True)

2. 表单层使用Formset处理多组束流参数

新建/修改forms.py:

from django import forms
from django.forms import formset_factory
from .models import BeamRequest, BeamParameter

# 主请求表单
class BeamRequestForm(forms.ModelForm):
    class Meta:
        model = BeamRequest
        exclude = []

# 单组束流参数表单
class BeamParameterForm(forms.ModelForm):
    class Meta:
        model = BeamParameter
        fields = ['Hours', 'Ion_Species', 'Energy', 'Flux']

# 束流参数表单集,最多4组
BeamParameterFormSet = formset_factory(BeamParameterForm, extra=4, max_num=4, can_delete=False)

3. 修改模板(唯一id + 动态联动适配)

你之前贴的是模板文件不是forms.py,修改如下:

{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% load static %}

{% block extrahead %}
<!-- 加载smart_selects required JS 保证联动正常 -->
<script type="text/javascript" src="{% static 'smart-selects/admin/js/chainedfk.js' %}"></script>
{% endblock %}

{% block title %}{{ title }}{% endblock %}

{% block content %}
<form method="post" novalidate>
    {% csrf_token %}
    <div class="Error_Messages">
        {% for error in form.non_field_errors %}
            {{ error }}
        {% endfor %}
        {% for error in formset.non_form_errors %}
            {{ error }}
        {% endfor %}
    </div>
    <br><br>
    <b>Internal contact information</b>
    <hr class="divider">

    <div class="row">
        <div class="col-6">
         {{ form.Project_Code|as_crispy_field }}
        </div>
        <div class="col-6">
         {{ form.Partrec_Contact_Name|as_crispy_field }}
     </div>
    </div>

    <div class="row">
        <div class="col-6">
         {{ form.Pac_Rate|as_crispy_field }}
        </div>
        <div class="col-6">
         {{ form.Partrec_Contact_Email|as_crispy_field }}
     </div>
    </div>

    <div class="row">
        <div class="col-6">
         {{ form.Previous_Experiment|as_crispy_field }}
        </div>
    </div>

    <div class="row">
        <div class="col-6">
         {{ form.Status|as_crispy_field }}
        </div>
    </div>
    <br><br>
    <b>External contact information</b>
    <hr class="divider">

    <div class="row justify-content-center">
     <div class="col-6">
        {{ form.Project_Title|as_crispy_field }}
     </div>
    </div>

    <div class="row">
     <div class="col-6">
        {{ form.Spokesperson_Name|as_crispy_field }}
     </div>
     <div class="col-6">
        {{ form.Spokesperson_Adress|as_crispy_field }}
     </div>
    </div>

    <div class="row">
     <div class="col-6">
        {{ form.Spokesperson_Phonenumber|as_crispy_field }}
     </div>
     <div class="col-6">
        {{ form.Spokesperson_Email|as_crispy_field }}
     </div>
    </div>
    <div class="row">
     <div class="col-4">
        {{ form.Collaborator_Name|as_crispy_field }}
     </div>
     <div class="col-4">
        {{ form.Collaborator_Nationality|as_crispy_field }}
     </div>
     <div class="col-4">
        {{ form.Collaborator_Home_Institute|as_crispy_field }}
     </div>
    </div>
     <br><br>
     <b>Beam information</b>
     <hr class="divider">

    <div class="row">
        <div class="col-6">
         {{ form.Start_Date|as_crispy_field }}
        </div>
        <div class="col-6">
         {{ form.End_Date|as_crispy_field }}
        </div>
    </div>
    <div class="justify-content-center">
        <div class="col-6">
        {{ form.Different_Beams|as_crispy_field }}
        </div>
    </div>
    <div class="row justify-content-center beam-formset">
        <!-- 渲染表单集 每个表单的id自动带前缀唯一标识 -->
        {{ formset.management_form }}
        {% for beam_form in formset %}
        <div class="col-6 beam-group" data-index="{{ forloop.counter0 }}">
            {{ beam_form|crispy }}
            <hr>
        </div>
        {% endfor %}
    </div>
    <div class="row justify-content-center">
      {{ form.Requiered_Equipment|as_crispy_field }}
      {{ form.Special_Requirements|as_crispy_field }}
      {{ form.Special_Safety_Procedures|as_crispy_field }}
      {{ form.Lab_Support_Requirements|as_crispy_field }}
      {{ form.Funded|as_crispy_field }}
      {{ form.Summary|as_crispy_field }}
    </div>
    <div class="row justify-content-center">
        <button type="submit" class="btn btn-success">Save New Request</button>
    </div>
</form>
<br>

<script type="text/javascript">
    // 初始隐藏所有束流表单,默认只显示第1组
    $('.beam-group').hide();
    $('.beam-group:eq(0)').show();

    $('#id_Different_Beams').on('change', function() {
        let count = parseInt($(this).val());
        $('.beam-group').hide();
        // 显示对应数量的表单
        for(let i=0; i<count; i++){
            $(`.beam-group[data-index="${i}"]`).show();
        }
        // 重新初始化smart_selects联动
        initChainedFK();
    });
</script>
{% endblock %}

4. 修改视图层处理多组数据提交

修改views.py:

from django.http import Http404, HttpResponse
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.admin.views.decorators import staff_member_required

from .models import BeamRequest
from .forms import BeamRequestForm, BeamParameterFormSet

def beam_request_home_page(request):
    page_title = 'Beam Request'
    template_name = 'request/home.html'
    context = {"title": page_title}
    return render(request, template_name, context)

@staff_member_required
def beam_request_create_page(request):
    page_title = 'Create new Request'
    template_name = 'request/create.html'

    if request.method == 'POST':
        form = BeamRequestForm(request.POST)
        formset = BeamParameterFormSet(request.POST)
        if form.is_valid() and formset.is_valid():
            # 先保存主请求
            req = form.save()
            # 保存所有束流参数
            for beam_form in formset:
                # 跳过未显示的空表单
                if beam_form.cleaned_data:
                    beam = beam_form.save(commit=False)
                    beam.request = req
                    beam.save()
            return redirect('beam_request_home_page')
    else:
        form = BeamRequestForm()
        formset = BeamParameterFormSet()
    
    context = {"title": page_title, "form": form, "formset": formset}
    return render(request, template_name, context)

额外说明

  • 表单集中每个表单的字段会自动生成唯一id,格式为id_form-0-Ion_Species、id_form-1-Ion_Species,不会出现id重复导致的联动失效问题
  • 一对多的存储结构后续查询、修改单组束流参数更方便,比直接存JSON数组更符合Django ORM的使用习惯

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.15 18:06:03