【问题标题】:dynamically add field to a form动态添加字段到表单
【发布时间】:2011-09-02 18:38:55
【问题描述】:

我的表单中有 3 个字段。 我有一个提交按钮和一个“添加附加字段”按钮。 我知道我可以在表单类中使用__init__ 方法添加字段。

我是 Python 和 Django 的新手,遇到一个初学者问题;我的问题是:

当我点击“添加附加字段”按钮时,添加附加字段的过程是什么?

表单是否必须再次呈现?

我如何以及何时拨打__init__ 或者我什至必须拨打它?

如何将参数传递给__init__

【问题讨论】:

标签: python django django-forms


【解决方案1】:

您的表单必须基于从您的 POST 传递给它的一些变量来构建(或盲目检查属性)。每次重新加载视图时都会构建表单本身,无论是否出错,因此 HTML 需要包含有关有多少字段的信息,以构建正确数量的字段进行验证。

我会以FormSets 的工作方式看待这个问题:有一个隐藏字段包含活动表单的数量,并且每个表单名称都带有表单索引。

事实上,你可以创建一个字段FormSet

https://docs.djangoproject.com/en/dev/topics/forms/formsets/#formsets

如果您不想使用FormSet,您始终可以自己创建此行为。

这是一个从头开始制作的 - 它应该会给你一些想法。它还回答了您关于将参数传递给 __init__ 的问题 - 您只需将参数传递给对象构造函数:MyForm('arg1', 'arg2', kwarg1='keyword arg')

表格

class MyForm(forms.Form):
    original_field = forms.CharField()
    extra_field_count = forms.CharField(widget=forms.HiddenInput())

    def __init__(self, *args, **kwargs):
        extra_fields = kwargs.pop('extra', 0)

        super(MyForm, self).__init__(*args, **kwargs)
        self.fields['extra_field_count'].initial = extra_fields

        for index in range(int(extra_fields)):
            # generate extra fields in the number specified via extra_fields
            self.fields['extra_field_{index}'.format(index=index)] = \
                forms.CharField()

查看

def myview(request):
    if request.method == 'POST':
        form = MyForm(request.POST, extra=request.POST.get('extra_field_count'))
        if form.is_valid():
            print "valid!"
    else:
        form = MyForm()
    return render(request, "template", { 'form': form })

HTML

<form>
    <div id="forms">
        {{ form.as_p }}
    </div>
    <button id="add-another">add another</button>
    <input type="submit" />
</form>

JS

<script>
let form_count = Number($("[name=extra_field_count]").val());
// get extra form count so we know what index to use for the next item.

$("#add-another").click(function() {
    form_count ++;

    let element = $('<input type="text"/>');
    element.attr('name', 'extra_field_' + form_count);
    $("#forms").append(element);
    // build element and append it to our forms container

    $("[name=extra_field_count]").val(form_count);
    // increment form count so our view knows to populate 
    // that many fields for validation
})
</script>

【讨论】:

  • 我还将为用户可以请求的附加字段的数量添加一个合理的默认最大值。否则,有人可能会提交一个任意大的数字,这会让您的申请无法处理。
  • 如果extra_fields 取自帖子,您需要将其转换为int 以使range 函数不显示错误。
  • 这段代码有错误。 form_count = $("[name=extra_field_count");以后你这样做没有任何意义:form_count ++;其次,如果您不创建任何额外的输入元素,则会出现错误。
  • @user1919 增加表单计数的目的是让服务器知道要从多少个字段开始,从而知道下一个表单字段编号是什么。我确实看到它会导致重复的字段,并且应该首先放置 form_count++。
  • @user1919 很好的发现。这主要是一个关于如何做到这一点的概念的示例,您可以在其中填写空白。更新。目的是接收当前存在的字段数。例如,假设您发布表单,并且由于某些表单元素未验证而出现错误。返回的 HTML 响应需要包含字段计数,这样当您通过 JS 添加另一个输入时,会附加正确的输入名称。 IE。渲染表格。添加 3 个字段。邮政。添加另一个字段。第 4 个字段需要正确的索引 vs 0。至于您将数据传递到视图的问题,让我看看。
【解决方案2】:

我曾经遇到过一个案例,我必须动态地创建带有动态字段的表单。我用这个技巧做的:

from django import forms

...

dyn_form = type('DynForm',  # form name is irrelevant
                (forms.BaseForm,),
                {'base_fields': fields})

请参阅此链接了解更多信息: Dynamic Forms

但除此之外,我还必须注入字段,即在表单类创建后动态添加字段。

dyn_form.base_fields['field1'] = forms.IntegerField(widget=forms.HiddenInput(), initial=field1_val)
dyn_form.base_fields['field2'] = forms.CharField(widget=forms.HiddenInput(), initial=field2_val)

这奏效了。

【讨论】:

  • 这确实有效!我需要动态创建一个表单类,将它传递给一个表单集,它就像一个魅力!谢谢!
  • 效果很好!谢谢@Al Conrad。此外,zerowithdot 在他的博客zerowithdot.com/django-dynamic-forms 中说明了这种技术的一种变体,它使用派生的 Form 类,其中字段直接注入作为它的类属性,如下所示:DynamicIngredientsForm = type('DynamicIngredientsForm', (IngredientsForm,), new_fields_dict)
【解决方案3】:

一种没有javascript且js中没有描述字段类型的方式:

Python

 def __init__(self, *args, **kwargs):
        super(Form, self).__init__(*args, **kwargs)

        ##ajouts des champs pour chaque chien
        for index in range(int(nb_dogs)):
            self.fields.update({
                'dog_%s_name' % index: forms.CharField(label=_('Name'), required=False, max_length=512),
            })

 def fields_dogs(self):
        fields = []
        for index in range(int(nb_dogs)):
            fields.append({
                'name': self['dog_%s_name' % index],
            })
        return fields

模板

{% for field_dog in f.fields_dogs %}
        <thead>
            <tr>
                <th style="background-color: #fff; border-width: 0px;"></th>
                <th>{% trans 'Dog' %} #{{forloop.counter}}</th>
                <th>{% trans 'Name' %}</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td style="background-color: #fff; border-width: 0px;"></td>
                <td style="background-color: #fff; border-width: 0px;"></td>
                <td>{{field_dog.name.errors}}{{field_dog.name}}</td>
            </tr>
            <tr>
                <td style="padding: 10px; border-width: 0px;"></td>
            </tr>
        </tbody>
{% endfor %}

【讨论】:

    【解决方案4】:

    此答案基于@Yuji'Tomita'Tomita 的一些改进和更改。

    尽管@Yuji'Tomita'Tomita 的回答很棒,并且很好地说明了构建“在 django 表单中添加额外字段”功能所遵循的方向,但我发现某些部分存在一些问题代码。

    这里我根据@Yuji'Tomita'Tomita 的初步提议提供我的工作代码:

    视图(在 view.py 文件中)

    视图并没有真正改变:

    def myview(request):
    
      if request.method == 'POST':
    
        form = MyForm(request.POST, extra=request.POST.get('total_input_fields'))
    
          if form.is_valid():
            print "valid!"
          else:
            form = MyForm()
    return render(request, "template", { 'form': form })
    

    表单(在 form.py 文件中)

    class MyForm(forms.Form):
    
        empty_layer_name = forms.CharField(max_length=255, required=True, label="Name of new Layer")
    
        total_input_fields = forms.CharField(widget=forms.HiddenInput())
    
    
        def __init__(self, *args, **kwargs):
    
          extra_fields = kwargs.pop('extra', 0)
    
          # check if extra_fields exist. If they don't exist assign 0 to them
          if not extra_fields:
             extra_fields = 0
    
          super(MyForm, self).__init__(*args, **kwargs)
          self.fields['total_input_fields'].initial = extra_fields
    
          for index in range(int(extra_fields)):
            # generate extra fields in the number specified via extra_fields
            self.fields['extra_field_{index}'.format(index=index)] = forms.CharField()
    

    模板 HTML

    <form id="empty-layer-uploader" method="post" enctype="multipart/form-data" action="{% url "layer_create" %}">
            <div id="form_empty_layer">
              <input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
                {{ form.errors }}
                {{ form.non_field_errors }}
                {% if errormsgs %}
                  {% for value in errormsgs %}
                    </p>  {{ value }} </p>
                  {% endfor %}
                {% endif %}
                {% for error in form_empty_layer.non_field_errors %}
                  {{ error }} </br>
                {% endfor %}
                </br>
                {% for field in form_empty_layer.visible_fields %}
                  {{ field }} </br>
                {% endfor %}
            </div>
            </br>
            <button type="button" id="add-another">add another</button> </br> </br>
            <button type="submit" id="empty-layer-button" name="emptylayerbtn">Upload</button>
            </br></br>
            // used in order to save the number of added fields (this number will pass to forms.py through the view)
            <input type="text" name="total_input_fields"/>
    </form>
    

    模板 Jquery

    // check how many times elements with this name attribute exist: extra_field_*
    form_count = $('input[name*="extra_field_*"]').length;
    
    // when the button 'add another' is clicked then create a new input element
    $(document.body).on("click", "#add-another",function(e) {
      new_attribute = $('<input type="text"/>');
      // add a name attribute with a corresponding number (form_count)
      new_attribute.attr('name', 'extra_field_' + form_count);
      // append the new element in your html
      $("#form_empty_layer").append(new_attribute);
      // increment the form_count variable
      form_count ++;
      // save the form_count to another input element (you can set this to invisible. This is what you will pass to the form in order to create the django form fields
      $("[name=total_input_fields]").val(form_count);
    
    })
    

    【讨论】:

    • 如何使用 4 个新文本框保存此表单?
    【解决方案5】:

    Yuji 'Tomita' Tomita 的解决方案是您能找到的最好的解决方案,但假设您有一个多步骤表单并且您使用 django-formtools 应用程序,您将遇到一些必须处理的问题。谢谢 Yuji 'Tomita' Tomita,你帮了我很多 :)

    forms.py

    class LicmodelForm1(forms.Form):
         othercolumsvalue = forms.IntegerField(min_value=0, initial=0)
    class LicmodelForm2(forms.Form):
        def __init__(self, *args, **kwargs):
        extra_fields = kwargs.pop('extra', 0)
    
        super(LicmodelForm2, self).__init__(*args, **kwargs)
    
        for index in range(int(extra_fields)):
            # generate extra fields in the number specified via extra_fields
            self.fields['othercolums_{index}'.format(index=index)] = \
                forms.CharField()
            self.fields['othercolums_{index}_nullable'.format(index=index)] = \
                forms.BooleanField(required=False)
    

    对于多步骤表单,您不需要额外的字段,在此代码中,我们在第一步中使用 othercolumsvalue 字段。

    views.py

    class MyFormTool(SessionWizardView):
    def get_template_names(self):
        return [TEMPLATES[self.steps.current]]
    
    def get_context_data(self, form, **kwargs):
        context = super(MyFormTool, self).get_context_data(form=form, **kwargs)
        data_step1 = self.get_cleaned_data_for_step('step1')
        if self.steps.current == 'step2':
    
            #prepare tableparts for the needLists
            needList_counter = 0
            for i in self.wellKnownColums:
                if data_step1[i] is True:
                    needList_counter = needList_counter + 1
                    pass
    
            #prepare tableparts for othercolums
            othercolums_count = []
            for i in range(0, data_step1['othercolumsvalue']):
                othercolums_count.append(str(i))
    
            context.update({'step1': data_step1})
            context.update({'othercolums_count': othercolums_count})
    
        return context
    
    def get_form(self, step=None, data=None, files=None):
        form = super(MyFormTool, self).get_form(step, data, files)
    
        if step is None:
            step = self.steps.current
    
        if step == 'step2':
            data = self.get_cleaned_data_for_step('step1')
            if data['othercolumsvalue'] is not 0:
                form = LicmodelForm2(self.request.POST,
                                     extra=data['othercolumsvalue'])
        return form
    
    def done(self, form_list, **kwargs):
        print('done')
        return render(self.request, 'formtools_done.html', {
            'form_data' : [form.cleaned_data for form in form_list],
            })
    

    通过覆盖 get_form()get_context_data() 函数,您可以在表单被渲染之前覆盖它。您的模板文件也不再需要 JavaScript:

                {% if step1.othercolumsvalue > 0 %}
                <tr>
                    <th>Checkbox</th>
                    <th>Columname</th>
                </tr>
                {% for i in othercolums_count %}
                    <tr>
                        <td><center><input type="checkbox" name="othercolums_{{ i }}_nullable" id="id_othercolums_{{ i }}_nullable" /></center></td>
                        <td><center><input type="text" name="othercolums_{{ i }}" required id="id_othercolums_{{ i }}" /></center></td>
                    </tr>
                {% endfor %}
            {% endif %}
    

    由于名称相同,步骤 2 中动态生成的字段也从 formtools 中重新整理。但是要到达那里,您必须解决 for-each 模板循环,如您所见:

    来自 get_context_data() 函数

            othercolums_count = []
            for i in range(0, data_step1['othercolumsvalue']):
                othercolums_count.append(str(i))
    

    【讨论】:

      猜你喜欢
      • 2016-08-26
      • 1970-01-01
      • 2015-02-14
      • 2017-10-07
      • 2011-11-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-01
      相关资源
      最近更新 更多