动态添加 Django 表单集实例并使用 HTMX POST 的正确方法是什么?

Dan*_*iel 3 django formset htmx

我正在使用htmx i制作带有嵌套动态表单集的表单(想要逃避 usign JS,但如果别无选择...)来实例化更多表单集字段以制作动态嵌套表单,但是当我POST时,仅来自1 个实例(最后一个)的数据被 POSTed,表单的其余部分正确 POST,并且获取与Chlid formset Child modelParent model

我阅读了有关如何POST 表单集实例的django 文档,并尝试将其应用到我的代码中,我也知道如何同时发布这 两个实例。对于表单集,我正在向包含子表单集的部分模板发出 htmx get 请求hx-get ,效果很好,唯一的问题是这总是将表单集返回到客户端,因此对于POST数据会重复每个字段的次数,并且只获取最后一个实例中放置的数据,但是我尝试更改表单集上的值以使更多表单直立,这给出了预期的结果,每个表单一个子实例,所以我的问题是htmx以及我调用实例的方式。ParentChildform-0xextra=intextra=intnew Child formset

这是我的代码。(我计划在此表单中嵌套更多子表单集,因此为了方便起见,我将其称为sformset)

****views.py****

def createPlan(request):#Requst for the Parent form 

    form = PlanForm(request.POST or None)
    sformset = StructureFormset(request.POST or None) #Nesting the Child formset

    context = {
        'form':form,
        'sformset':sformset,
        }

    if request.method == 'POST':

        print(request.POST)
        if form.is_valid() and sformset.is_valid():

            plan = form.save(commit=False)
            print(plan)
            plan.save()
             
            sform = sformset.save(commit=False)     
            for structure in sform:

                structure.plan = plan
                structure.save()

    return render(request, 'app/plan_forms.html', context)


def addStructure(request):

    sformset = StructureFormset(queryset=Structure.objects.none())#add a empty formset instance 
    
    context = {"sformset":sformset}

    return render(request, 'app/formsets/structure_form.html', context)
Run Code Online (Sandbox Code Playgroud)
****forms.py****

StructureFormset = modelformset_factory(Structure,
        fields = (
            'material_type',
            'weight',
            'thickness',
            'provider'
        ))
Run Code Online (Sandbox Code Playgroud)
****relevant part for plan_forms.html template****

<form method="POST">
  {% csrf_token %}
  <div class="col-12 px-2">
    <div class="row px-3 py-1">
      <div class="col-3 px-1">{{ form.format }}</div>
      <div class="col-3 px-1">{{ form.pc }}</div>
      <div class="col-3 px-1">{{ form.revission }}</div>
      <div class="col-3 px-1">{{ form.rev_date }}</div>
    </div>
    <div class="row px-3 py-1">
      <div class="col-3 px-1">{{ form.client }}</div>
      <div class="col-3 px-1">{{ form.product }}</div>
      <div class="col-3 px-1">{{ form.gp_code }}</div>
      <div class="col-3 px-1">{{ form.code }}</div>
    </div>
  </div>
  <div>
    <table>
      <tbody style="user-select: none;" id="structureforms" hx-sync="closest form:queue">
        <!--Structure formset goes here-->
      </tbody>
      <tfoot>
        <a href="" hx-get="{% url 'structure-form' %}" hx-swap="beforeend" hx-target="#structureforms">
          Add structure <!--Button to call structure formset-->
        </a>
      </tfoot>
    </table>
  </div>
  <div class="col-12 px-2">
    <div class="row px-4 py-1">{{ form.observation }}</div>
    <div class="row px-4 py-1">{{ form.continuation }}</div>
    <div class="row px-4 py-1">{{ form.dispatch_conditions }}</div>
    <div class="row px-3 py-1">
      <div class="col-6 px-1">{{ form.elaborator }}</div>
      <div class="col-6 px-1">{{ form.reviewer }}</div>
    </div>
  </div>
  <button type="submit">Submit</button>
</form>
Run Code Online (Sandbox Code Playgroud)
****formsets/structure_form.html****

<tr>
  <td class="col-12 px-1">
    {{ sformset }}
  </td>
</tr>
Run Code Online (Sandbox Code Playgroud)
**** relevant urls.py****

urlpatterns = [
    path('create_plan/', views.createPlan, name='create_plan'),
    path('htmx/structure-form/', views.addStructure, name='structure-form')]
Run Code Online (Sandbox Code Playgroud)

此外,我使用字段和内联在admin.py中构建的表单正是我想要的原始产品(除了初始表单集和样式的数量)

McP*_*son 5

我使用强大的动态表单集的方法HTMX:

拥有一个呈现表单集实例的视图empty_form(即formset.empty_form)。这将返回一个表单,__prefix__其部分通常包含表单编号(即,而不是form-0-field_name,它将是form-__prefix__-field_name)。这里的技巧是简单地将 替换__prefix__为适当的表格编号。

@Matt 的答案通过迭代表单并进行相关替换来实现这一点。虽然这工作正常,但我认为更简单的方法是在实例化表单集后更改表单集的前缀。这样,您就不会使用build_new_formset辅助函数。

sformset.management_form在客户端: 渲染plan_forms.html. form-TOTAL_FORMS并使用属性在 HTMX 的 GET 请求中包含表单集的 [隐藏输入字段] 的值hx-vals(请注意,我使用的是 jquery 选择器,这只是为了方便。Vanilla JS 也应该可以工作)。

plan_forms.html

<form method="POST">
   {% csrf_token %}
   {{sformset.management_form}} 
        ...
      <tbody style="user-select: none;" id="structureforms" hx-sync="closest form:queue">
        <!--Structure formset goes here-->
      </tbody>
      <tfoot>
        <button hx-get="{% url 'structure-form' %}" 
           hx-swap="beforeend" 
           hx-target="#structureforms"
           hx-get="{% url 'structure-form' %}"
           hx-vals='js:{totalForms: $("#id_form-TOTAL_FORMS").val()}' \\ New>
          Add structure <!--Button to call structure formset-->
        </button>
      </tfoot>
        ...
</form>
Run Code Online (Sandbox Code Playgroud)

在 htmx 发出请求后,需要更新表单集中的表单总数(每次添加表单集时都会增加)。如果你想严格使用 HTMX,你可以使用hx-on允许你监听事件并运行内联 JS 的属性。然而,hyperscript由于 isincrement命令,我发现这更容易。

plan_forms.html

<form method="POST">
        ...
      <tbody style="user-select: none;"
             id="structureforms" 
             hx-sync="closest form:queue"
             _="on htmx:afterSettle increment #id_form-TOTAL_FORMS's value">
        <!--Structure formset goes here-->
      </tbody>
       ...
</form>
Run Code Online (Sandbox Code Playgroud)

然后你可以在你的视图中处理请求: views.py

      ...
def createPlan(request): #Requst for the Parent form 
    form = PlanForm(request.POST or None)
    sformset = StructureFormset(request.POST or None) #Nesting the Child formset
      ...

def addStructure(request):
    sformset = StructureFormset(queryset=Structure.objects.none()).empty_form # Creates an empty formset
    form_number = int(request.GET.get("totalForms")) - 1 # forms are numbered from 0, so they will always be 1 less than formset's total forms.
    sformset.prefix = sformset.prefix.replace("__prefix__", str(form_number)) # override the formset's prefix, replacing the __prefix__
    context = {"sformset":sformset}
    return render(request, 'app/formsets/structure_form.html', context)
Run Code Online (Sandbox Code Playgroud)

我想差不多就是这样了。

注释 您hx-vals可以使用hx-include

...
hx-include='[name="form-TOTAL_FORMS"]'
...
Run Code Online (Sandbox Code Playgroud)

鉴于addStructure您必须通过以下方式获得价值request.GET.get("form0TOTAL_FORMS")