【问题标题】:Making a Django form class with a dynamic number of fields制作具有动态字段数的 Django 表单类
【发布时间】:2011-07-25 14:33:31
【问题描述】:

我正在开发类似在线商店的东西。我正在制作客户购买商品的表格,她可以选择她想购买的商品数量。但是,在她购买的每件商品上,她都需要选择颜色。所以字段的数量是不固定的:如果客户购买 3 件商品,她应该得到 3 个<select> 用于选择颜色的盒子,如果她购买 7 件商品,她应该得到 7 个这样的 <select> 盒子。

我将使用 JavaScript 使 HTML 表单域出现和消失。但是如何在我的 Django 表单类中处理这个问题?我看到表单字段是类属性,所以我不知道如何处理某些表单实例应该有 3 个颜色字段和一些 7 个的事实。

有什么线索吗?

【问题讨论】:

    标签: python django forms


    【解决方案1】:

    你可以这样做

    def __init__(self, n,  *args, **kwargs):
      super(your_form, self).__init__(*args, **kwargs)
      for i in range(0, n):
        self.fields["field_name %d" % i] = forms.CharField()
    

    当您创建表单实例时,您只需这样做

    forms = your_form(n)
    

    这只是基本思想,您可以将代码更改为您想要的任何内容。 :D

    【讨论】:

    • 这很好用。如果您传递一个 FormFields 列表而不是仅传递“n”,则可以稍微修改循环以动态生成任意表单! (这就是我所做的) - 上面的示例来自所选答案 BTW 中的链接。
    【解决方案2】:

    Jacob Kaplan-Moss 有一篇关于动态表单字段的大量文章: http://jacobian.org/writing/dynamic-form-generation/

    本质上,您在实例化过程中向表单的self.fields 字典添加了更多项目。

    【讨论】:

    • 在这种情况下你将如何传递初始数据?
    • 不错的链接,但这个用例是专门用于表单集的。
    【解决方案3】:

    另一种方法:我们可以使用 mixin 覆盖字段,在 generate_dynamic_fields 中返回动态字段的 OrderedDict,而不是破坏正常的字段初始化流程,该动态字段将在其设置时添加。

    from collections import OrderedDict
    
    class DynamicFormMixin:
        _fields: OrderedDict = None
    
        @property
        def fields(self):
          return self._fields
    
        @fields.setter
        def fields(self, value):
            self._fields = value
            self._fields.update(self.generate_dynamic_fields())
    
        def generate_dynamic_fields(self):
            return OrderedDict()
    

    一个简单的例子:

    class ExampleForm(DynamicFormMixin, forms.Form):
        instance = None
    
        def __init__(self, instance = None, data=None, files=None, auto_id='id_%s', prefix=None, initial=None,
                     error_class=ErrorList, label_suffix=None, empty_permitted=False, field_order=None,
                     use_required_attribute=None, renderer=None):
            self.instance = instance
            super().__init__(data, files, auto_id, prefix, initial, error_class, label_suffix, empty_permitted, field_order,
                             use_required_attribute, renderer)
    
        def generate_dynamic_fields(self):
            dynamic_fields = OrderedDict()
            instance = self.instance
            dynamic_fields["dynamic_choices"] = forms.ChoiceField(label=_("Number of choices"),
                                                                  choices=[(str(x), str(x)) for x in range(1, instance.number_of_choices + 1)],
                                                                  initial=instance.initial_choice)
            return dynamic_fields
    

    【讨论】:

      【解决方案4】:

      我会这样做的方式如下:

      1. 创建一个继承自froms.Form 的“空”类,如下所示:

        class ItemsForm(forms.Form):
            pass
        
      2. 构建作为实际表单的表单对象的字典,其组成将取决于上下文(例如,您可以从外部模块导入它们)。例如:

        new_fields = {
            'milk'  : forms.IntegerField(),
            'butter': forms.IntegerField(),
            'honey' : forms.IntegerField(),
            'eggs'  : forms.IntegerField()}
        
      3. 在视图中,你可以使用python原生的“type”函数来动态生成一个Form类,其字段数是可变的。

        DynamicItemsForm = type('DynamicItemsForm', (ItemsForm,), new_fields)
        
      4. 将内容传递给表单并在模板中呈现:

        Form = DynamicItemsForm(content)
        context['my_form'] = Form
        return render(request, "demo/dynamic.html", context)
        

      “内容”是字段值的字典(例如,即使 request.POST 也可以)。 你可以看到我的整个例子解释here

      【讨论】:

        【解决方案5】:

        这是另一种选择:formset 怎么样? 由于您的字段都是相同的,这正是表单集的用途。

        django 管理员使用FormSets + 一点 javascript 添加任意长度的内联。

        class ColorForm(forms.Form):
            color = forms.ChoiceField(choices=(('blue', 'Blue'), ('red', 'Red')))
        
        ColorFormSet = formset_factory(ColorForm, extra=0) 
        # we'll dynamically create the elements, no need for any forms
        
        def myview(request):
            if request.method == "POST":
                formset = ColorFormSet(request.POST)
                for form in formset.forms:
                    print "You've picked {0}".format(form.cleaned_data['color'])
            else:
                formset = ColorFormSet()
            return render(request, 'template', {'formset': formset}))
        

        JavaScript

            <script>
                $(function() {
                    // this is on click event just to demo.
                    // You would probably run this at page load or quantity change.
                    $("#generate_forms").click(function() {
                        // update total form count
                        quantity = $("[name=quantity]").val();
                        $("[name=form-TOTAL_FORMS]").val(quantity);  
        
                        // copy the template and replace prefixes with the correct index
                        for (i=0;i<quantity;i++) {
                            // Note: Must use global replace here
                            html = $("#form_template").clone().html().replace(/__prefix_/g', i);
                            $("#forms").append(html);
                        };
                    })
                })
            </script>
        

        模板

            <form method="post">
                {{ formset.management_form }}
                <div style="display:none;" id="form_template">
                    {{ formset.empty_form.as_p }}
                </div><!-- stores empty form for javascript -->
                <div id="forms"></div><!-- where the generated forms go -->
            </form>
            <input type="text" name="quantity" value="6" />
            <input type="submit" id="generate_forms" value="Generate Forms" />
        

        【讨论】:

        • 如果您的模型中有更多字段,/__prefix__/g 而不是 'prefix' 可能会非常有用;)
        • 最后一行视图中的一些小错字 (1):return render(request, 'template', {'formset': formset})) (一个括号太多) (2) 在模板中,忘记了删除 ' 行: html = $("#form_template").clone().html().replace(/__prefix_/g', i);必须是:$("#form_template").clone().html().replace(/__prefix_/g, i);
        • 很好的例子,从中学到了很多,只是修复了一些发生的错误,第三个(3):在视图中,您还必须调用函数 form.is_valid() 来为表单提供清理数据属性
        • 那么表单是如何提交的呢?我似乎无法在我的视图中获得表单字段的值。我什至在表单中添加了一个提交按钮。我在'formview 中的cleaned_data 都是空的。如何发送POST 请求?
        • 现在提交。这行print "You've picked {0}".format(form.cleaned_data['color']) 导致KeyError。我的formset.cleaned_data 没有被调用。每个字段都返回空。表单集返回有效,但我无法获取任何值字段值
        猜你喜欢
        • 1970-01-01
        • 2011-09-22
        • 1970-01-01
        • 1970-01-01
        • 2021-03-24
        • 2011-02-07
        • 2021-12-18
        相关资源
        最近更新 更多