【问题标题】:Clean Django form fields with same name清理具有相同名称的 Django 表单字段
【发布时间】:2018-06-26 19:52:26
【问题描述】:

我有一个 django 模板,我在其中动态呈现多个字段(使用 ajax) 下面是一个 Django 表单(已在模板中呈现),其字段具有相同的名称。我想使用cleaned_data 方法来清理views.py 中的表单数据,然后再将它们存储到数据库中。

index.html

<div class="form-container">
    <!-- ASSUMING I HAVE ALREADY ADDED FIELDS DYNAMICALLY -->
    <form id = "orderForm" action="newPickupOrder/" method="post" name="processForm">
        <input type='text' name='this_field'>
        <input type='text' name='this_field'>
        <button type="submit">Submit</button>
    </form>
</div>   

<form id="addItemForm">
    {% csrf_token %}
    <!-- BUTTON TO ADD MORE FIELDS DYNAMICALLY -->
    <button id = "addItemButton">Add item</button>
</form>

<script>
    var addItemButton = document.querySelector('#addItemButton');

    addItemButton.onclick = function(){            

        $.ajax({
            type: 'POST',
            url: 'addItem/',
            data: addItemForm.serialize(),
            success: function (response) {
                 $("#orderForm").append(response);                     
                 console.log('Success');
            },
            error: function (response) {                    
                console.log('Error = '+response);
            }
        });
    };
</script>

forms.py

class ItemForm(forms.Form):
    this_field = forms.CharField()

urls.py

urlpatterns = [
    url(r'^newPickupOrder/$', views.pickup_order_view, name='new_pickup_order'),
]

views.py

def add_item(request):
    if request.method == 'POST':
        itemForm = ItemForm()
        return HttpResponse(itemForm.as_p())

def pickup_order_view(request):
    if request.method == 'POST':
        form = ItemForm(request.POST)
        same_name_fields = request.POST.getlist('this_field')  
        # WANT TO CLEAN DATA IN same_name_fields

        if form.is_valid():
            print(form.cleaned_data)
            # ONLY PRINTS THE LAST FIELD's DATA
            return HttpResponseRedirect('/viewPickupRequests')              

我面临的问题是,如果我使用form.cleaned_data['this_field'],则只会获取最后一个字段的数据,即在本例中,会获取并清理值为anotherTestValue 的字段。如果我使用request.POST.getlist('this_field') 获取数据,所有字段的数据都会被获取并存储为一个列表,但是,我不知道如何使用cleaned_data 方法来清理它。有没有办法将cleaned_data 方法应用于字段数据列表?

【问题讨论】:

  • 你是如何在表单中声明这个字段的?
  • @PauloScardine 我已对问题进行了编辑,请立即查看
  • 我认为您应该从 MultipleChoiceField 继承并禁用针对选择列表的验证才能完成此操作。不幸的是,我没有时间写一个正确的答案,对不起。

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


【解决方案1】:

对不起,我无法测试这是否有效,所以这不是一个真正的答案 - 但评论系统不适合更大的代码块,所以我在这里发布。

Django 表单缺少一个字段类型,该字段类型会呈现给具有相同名称的多个文本输入。正确的做法是编写一个新的表单字段类和一个新的小部件。由于您没有在模板中呈现表单(您仅将其用于验证),因此我将省略小部件部分。

class AcceptAnythingMultipleChoiceField(forms.MultipleChoiceField):
    def validate(self, value):
        if self.required and not value:
            raise ValidationError(
                self.error_messages['required'], 
                code='required'
            )

然后使用这个字段类而不是forms.CharField()(您可能需要传递一个空的选择参数)。

[更新]

所以本质上你说的是我需要创建新的表单字段类,然后每次用户想要添加新字段时将其渲染到模板?如果用户必须添加 15 个字段怎么办,那么我需要创建 15 个类!我认为这种方法不适用于需要生成的字段数量很大的场景。我觉得应该有一些我不知道的优雅方式来做到这一点——OP

不,这不是我要说的。您可能想要继承 MultipleHiddenInput 之类的东西并将 AcceptAnythingMultipleChoiceField.widget 设置为它。您必须基于MultipleHiddenInput 的模板创建一个新模板,并将输入type="hidden" 替换为type="text"(原始模板为django/forms/widgets/multiple_hidden.html)。

class AcceptAnythingWidget(MultipleHiddenInput):
    template_name = 'django/forms/widgets/multiple_visible.html'

class AcceptAnythingMultipleChoiceField(forms.MultipleChoiceField):
    widget = AcceptAnythingWidget
    def validate(self, value):
        if self.required and not value:
            raise ValidationError(
                self.error_messages['required'], 
                code='required'
            )

如果您使用,这应该会根据需要在前端为实例化表单呈现尽可能多的&lt;input name='this_field'&gt;

{{ form.this_field }}

在模板中,但不会动态添加/删除它们。

为此,您必须插入在小部件中动态添加/删除输入所需的 JavaScript,但我将把它留给您作为练习。查看文档中的 Form Assets (the Media class) 以了解如何执行此操作。

【讨论】:

  • 所以基本上你的意思是我需要创建新的表单字段类,然后每次用户想要添加新字段时将其渲染到模板?如果用户必须添加 15 个字段怎么办,那么我需要创建 15 个类!我认为这种方法不适用于需要生成的字段数量很大的场景。我觉得应该有一些我不知道的优雅方式来做到这一点
  • 不,这不是我要说的。我的答案仅针对服务器端,它适用于您提供的 HTML。如果它是单个实例,您可以将其留给前端以根据需要呈现尽可能多的&lt;input name='this_field'&gt;(或添加/删除它们)。如果您要经常使用它,则可以将 MultipleHiddenInput 之类的子类化并添加 javascript 以添加/删除项目,然后将 AcceptAnythingMultipleChoiceField.widget 设置为新的小部件。
  • 哦,是的!现在你这样说,在前端渲染这些表单比使用表单字段类更有意义。谢谢!
  • 我已经更新了答案以澄清这一点,但它仍然未经测试。
  • 对您的方法的修改是所有字段的数据可以打包到一个隐藏字段中(在每个字段数据的末尾附加一些分隔符),以便在views.py 中的数据可以分开(基于分隔符)并在拆分后单独存储。使用这种修改,就不需要创建多个隐藏字段,从具有相似名称的隐藏字段中获取数据的初始问题可能会再次出现。
【解决方案2】:

我认为您正在寻找的是表单集。 https://docs.djangoproject.com/en/2.0/topics/forms/formsets/

from django.forms import formset_factory

ItemFormSet = formset_factory(ItemForm, extra=2)

除了这个对象是iterable之外,你基本上可以像使用普通表单一样使用ItemFormSet。

如果您想动态添加项目,您还必须更改您的 jquery。网上有很多关于如何做到这一点的例子。总之你要做的是

  1. 克隆表单集中的表单之一
  2. 清除复制表单中的所有值
  3. 更新输入的ID(前缀)

使用 Formsets 并不能解决获取和验证的问题 具有相同名称的字段。问题依旧

但它确实会生成您想要的最终结果(见下文)。我的问题是为什么你需要有同名的输入?如果有一些使用这些名称的 jquery 东西,我看不出有任何理由说明您不能使用类似...的名称或为输入分配一个类。

def pickup_order_view(request):

    if request.method == 'GET':
        ItemFormSet = formset_factory(ItemForm, extra=5)
        item_formset = ItemFormSet()

        template = "some_template.html"
        template_context = {'item_formset': item_formset}
        return render(request, template, template_context)

    if request.method == 'POST':
        ItemFormSet = formset_factory(ItemForm)
        item_formset = ItemFormSet(request.POST)

        same_name_fields=[]
        if item_formset.is_valid():

            for item_form in item_formset:
                same_name_fields.append(item_form.cleaned_data['this_field'])

            print(same_name_fields)

模板

<form id = "orderForm" action="newPickupOrder/" method="post" name="processForm">
    {% csrf_token %}
    {{ item_formset.management_form }}
    {{ for item_form in item_formset }}
        {{ item_form.as_p }}
    {{ endfor }}

    <input type='submit' value='submit'>
</form>

转到 newPickupOrder/ ,填写 5 个字段,点击提交,然后观看它打印您的列表。

【讨论】:

  • 使用 Formsets 并不能解决获取和验证同名字段的问题。问题依然存在
  • 它确实会生成您想要的最终结果。我的问题是为什么你坚持在你的 html 中输入相同的名称值?
  • 我不坚持使用相同名称的字段。如果我可以用一个例子来解释这种情况,如果用户 1 想再添加 15-20 个字段,并记住这一点,我将额外设置为 15,这对于只想要 2-3 个字段的用户 2 来说是反直觉的,因为呈现时表单看起来很长。因此,如果我再次使用 JQuery 和 AJAX 渲染表单集以添加更多字段,则会再次出现类似字段名称的初始问题。正如@PauloScardine 所说,如果我完全在前端进行字段的动态渲染而不创建表单集,则问题将得到解决。
  • 这就是为什么你设置 extra 说,只是 1,然后使用 jquery 允许用户根据需要添加和删除表单。可以在这里找到很好的例子:stellarchariot.com/blog/2011/02/…
  • 伟大的链接分享伙伴!这比我将所有字段数据打包到隐藏字段中的方法要好。我会接受你的回答。
猜你喜欢
  • 2016-06-02
  • 2017-03-03
  • 1970-01-01
  • 2015-12-27
  • 2011-01-13
  • 2016-08-25
  • 2011-02-04
  • 2017-04-08
  • 1970-01-01
相关资源
最近更新 更多