【问题标题】:ManagementForm Data is Missing When Using Formset Prefix使用 Formset 前缀时缺少 ManagementForm 数据
【发布时间】:2015-07-09 12:13:11
【问题描述】:

有人知道为什么当我使用 Formset Prefix 时会引发 ManagementForm Data is Missing 吗?

从外壳

>>> from django import forms
>>> from django.forms.formsets import formset_factory
>>> 
>>> class CheckBox (forms.Form):
...     overwrite = forms.BooleanField (required = False)
... 
>>> 
>>> data = {
...     'form-TOTAL_FORMS': '2',
...     'form-INITIAL_FORMS': '0',
...     'form-MAX_NUM_FORMS': '3',
...     'checkbox-0-overwrite': True,
...     'checkbox-1-overwrite': False,
... }
>>> 
>>> CheckBoxFormSet = formset_factory (CheckBox)
>>> formset = CheckBoxFormSet (data)
>>> formset.is_valid ()
True
>>> formset.cleaned_data
[{}, {}]
>>> 

为表单集添加前缀

>>> formset = CheckBoxFormSet (data, prefix = 'checkbox')
>>> formset.is_valid ()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
.
.
.
django.core.exceptions.ValidationError: ['ManagementForm data is missing or has been tampered with']

Django Doc 提到使用前缀来区分“a”视图中的不同表单集。如果我在同一个视图中使用它,但以不同的方法处理不同的 HTML 页面,例如示例,这是否适用?执行示例中 Django 建议的操作也会触发 ManagementForm Data is Missing 错误。

例如:

forms.py

class NodeForm (forms.Form):

    cars = forms.CharField (required = False)
    trucks = forms.CharField (required = False)

class CheckBox (forms.Form):
    overwrite = forms.BooleanField (required = False)

views.py

def cars (request):

    CarsFormSet = formset_factory (CarsForm, formset = BaseNodeFormSet, extra = 2, max_num = 5)

    if request.method == 'POST':

        cars_formset = CarsFormSet (request.POST, prefix = 'carsform')

        if cars_formset.is_valid ():
            data = cars_formset.cleaned_data

            context = {'data': data}
            return render (request, 'vehicleform/response.html', context)
        else:
            cars_formset = CarsFormSet (prefix = 'carsform')

     context = {...previously entered data from POST...}

     return render (request, 'vehicleform/carsform.html', context)

def trucks (request):

    TrucksFormSet = formset_factory (TrucksForm, extra = 2, max_num = 5)

    if request.method == 'POST':

        trucks_formset = TrucksFormSet (request.POST, prefix = 'trucksform')

        if trucks_formset.is_valid ():
            data = truck_formset.cleaned_data

            context = {'data': data}
            return render (request, 'vehicleform/success.html', context)

        else:
            trucks_formset = TrucksFormSet (prefix = 'trucksform')

     return HttpResponse ('No overwrite data.')

更新 1
我已将其范围缩小到实际数据。由于某种原因,它不喜欢我的数据。

更新 2
我已经验证了表单中的名称和数据是相同的。当我在数据中声明 2 时,它只打印一个 checkbox-0-overwrite。想知道为什么 formset 不适用于复选框。

>>> CheckBoxFormSet = formset_factory (CheckBox)
>>> formset = CheckBoxFormSet (prefix = 'checkbox')
>>> 
>>> for form in formset:
...     print (form)
... 
<tr><th><label for="id_checkbox-0-overwrite">Overwrite:</label></th><td><input id="id_checkbox-0-overwrite" name="checkbox-0-overwrite" type="checkbox" /></td></tr>
>>> 

更新 3
我不知道发生了什么事了。这似乎生成了没有前缀的表单。插入前缀时仍然出现错误。

>>> CheckBoxFormSet = formset_factory (CheckBox)
>>> formset = CheckBoxFormSet (data)
>>> formset.is_valid ()
True
>>> for form in formset:
...     print (form)
... 
<tr><th><label for="id_form-0-overwrite">Overwrite:</label></th><td><input id="id_form-0-overwrite" name="form-0-overwrite" type="checkbox" /></td></tr>
<tr><th><label for="id_form-1-overwrite">Overwrite:</label></th><td><input id="id_form-1-overwrite" name="form-1-overwrite" type="checkbox" /></td></tr>
>>> 
>>> 
>>> data {
...    'form-TOTAL_FORMS': '2',
...    'form-INITIAL_FORMS': '0',
...    'form-MAX_NUM_FORMS': '3',
...    'checkbox-0-overwrite': True
}

更新 4
下面的 html 模板是由第一个表单汽车生成和创建的,正如我从上面的示例中更新的那样。第二种形式只在第一种形式传递的数据旁边插入复选框。在模板中显示表单集并单击提交仍然给我“ManagementForm”错误。我将尝试创建一个只有复选框的全新表单,看看是否有任何错误。

响应.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/html">
<head lang="en">
    <meta charset="UTF-8">
    {% load staticfiles %}
    <link rel="stylesheet" type="text/css" href="{% static 'nodeform/style.css' %}" >
    <title>Vehicle Information</title>
</head>
<body>
    <h1>Vehicle Information:</h1>
    <h4>Location: {{ location }}</h4>
    <form action="trucks" method="POST">{% csrf_token %}
    {{ checkbox_formset.management_form }}
       {% for form in checkbox_formset %}
        {{ form }}
       {% endfor %}
    <br>
    <p><input type="submit" value="Confirm">
    <a href="{% url 'carsform' %}">
        <button type="button">Cancel</button></a></p>
    </form>
</body>
</html>

更新 5
我不确定我是否理解正确,但我认为失败在于表单的操作以及我如何获取数据。初始表单 (carsform.html) 的表单标签没有任何动作:

carsform.html

<form action="" method="POST">{% csrf_token %}...</form>

它执行 POST,然后将收集到的信息传递到下一页/表单 (response.html)。此外,它向以前的数据添加了一组复选框,如下所示:

response.html

<form action="trucks" method="POST">{% csrf_token %}...</form>

输出:

Audi (Obtained from cars)    []  <---Checkbox inserted from response.html manually & obtaining data from method trucks
Toyota (Obtained from cars)  []  <---Checkbox inserted from response.html manually & obtaining data from method trucks

当用户点击“提交”时,response.html 表单将处理并再次“反转”回卡车。这次没有来自汽车方法的数据进行处理。这最终引发了 ManagementForm 错误。

我通过在初始页面 (carsform.html) 中插入 2 个表单集并单击提交来对此进行了测试。我在下一页/表单(response.html)上看到的结果既有第一个表单集的数据,也有第二个表单集的数据。

我的下一个问题是如何创建第二个表单(response.html)来获取数据而不报错?

【问题讨论】:

  • 嗯,我遇到的部分问题是,您并没有一开始就发布您想要做的事情。你为什么要把某人带到一个全新的页面只是为了检查一个框,为什么不把所有东西都放在一个页面上并发送一次表单数据?
  • 我不想用我冗长的目标来迷惑和吓唬人们,所以我不得不缩小到一个具体的问题。之所以不将所有内容都放在一个页面上,是因为表单需要验证和处理用户输入以确定是否存在重复。如果有,请为用户提供一种检查和确认覆盖或完全忽略它的方法。如果你有更好的方法来做到这一点,我很想听听。感谢您迄今为止的帮助。
  • 嗯,好吧,所以复选框是供用户确认覆盖的,对吧?你有没有考虑在用户提交之前使用 JS 来检查重复项?我也在考虑可能是一个向导,但这取决于我猜你想检查多少字段。
  • 没错。我已经有一个表单用来检查重复的后端 Python 程序。我只需要回复表单告诉我用户提交后是否覆盖它。

标签: python django django-forms django-views formset


【解决方案1】:

根据docs

重要的是要指出,您需要在两个 POST 和非 POST 情况,以便对其进行渲染和处理 正确。

因此,首先,在呈现空白表单(即不是 POST)时,您将拥有:

trucks_formset = TrucksFormSet(prefix ='trucksform')

您的“数据”也因前缀失败,因为您没有更改数据中字段的名称。前缀重命名您的字段。您可以尝试将表单集发布到模板,您将看到隐藏字段的名称。

 data = {
...     'checkbox-TOTAL_FORMS': '2',
...     'checkbox-INITIAL_FORMS': '0',
...     'checkbox-MAX_NUM_FORMS': '',
...     'checkbox-0-overwrite': True,
...     'checkbox-1-overwrite': False,
... }
>>> 
>>> CheckBoxFormSet = formset_factory (CheckBox, extra=1)
>>> formset = CheckBoxFormSet (data, prefix = 'checkbox')

【讨论】:

  • 我同意了,我在原始帖子后不久发布了更新 1。前缀有效,但数据无效,我认为这是因为 Django 没有 BooleanField 的表单集。
  • 额外在'checkbox-TOTAL_FORMS'中声明:'2'。我已经编辑了更新 #3 以表明它不是必需的。
  • 嗯。再一次,我将不得不坚持你在模板中打印出你的表格,而不是 SHELL,这样你就可以看到管理数据是什么。您没有在帖子中发布您所做的实际更改,因此很难判断您哪里出错了,但管理数据肯定存在问题。
  • 我想我发现了更新#5中描述的问题。
【解决方案2】:

问题在于方法cars 渲染到response.html 页面并在url http://..../vehicle/cars 而不是vehicle/trucks 处显示渲染的表单。引发“管理表单错误”是因为“POST”第二次发生,但仍处于 url 车辆/汽车形式而不是车辆/卡车形式。更新 5 暗示了这个问题。解决方案是简单地使用

return HttpResponseRedirect ('trucks')

render (request, 'vehicleform/trucksform.html', context)
return HttpResponseRedirect ('trucksform')

上述两种方法的区别在于,第一种方案是从第二种形式(trucksform)呈现数据,而第二种方案是从第一种形式(carsform)呈现数据。

为什么会有这么大的问题?好吧,因为我希望第一个表单在出现错误时重新显示而不重定向到另一个页面;因此,

<form action="" method="POST">

否则,设置

<form action="truck" method="POST">

不会造成这种混乱。

为了能够在单个视图中使用 2 个不同的表单集,请通过访问其直接 URL 分别测试每个页面/表单。一旦确认两个页面都按预期呈现和工作,请使用 HttpResponseRedirect。

感谢 onyeka 一路上的帮助。

【讨论】:

    【解决方案3】:

    这不完全是 OP 的问题,而是我搜索此错误时出现的第一个结果,所以我想我会分享我的前缀(包括在内)有什么问题,因为它并不明显。

    Django 表单框架automatically defines _id_for_label 如果您没有为表单字段的属性设置一个,但不会从表单集数据中删除它们。因此,如果您的标签是“my_label”,它将被呈现为“id_my_label”,并在 POST 数据中返回。

    我花了一段时间才在我的request.POST(下面的data)中发现了这一点

    MyFormSet = formset_factory(MyForm, prefix='my_form')
    
    data = {
        'id_result_form-MAX_NUM_FORMS': '1000', # note how django added 'id_'
        'id_result_form-INITIAL_FORMS': '0',
        'id_result_form-TOTAL_FORMS': '1',
    }
    
    formset = MyFormSet(data, prefix='my_form')
    formset.is_valid()
    >>> False
    
    formset = MyFormSet(data, prefix='id_my_form') # added 'id_'
    formset.is_valid()
    >>> True
    

    【讨论】:

      猜你喜欢
      • 2018-03-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-08
      • 2011-09-11
      • 2016-08-05
      相关资源
      最近更新 更多