带有动态表单的 Django FormWizard

Posted

技术标签:

【中文标题】带有动态表单的 Django FormWizard【英文标题】:Django FormWizard with dynamic forms 【发布时间】:2010-09-26 09:13:21 【问题描述】:

我想实现一个简单的两部分 FormWizard。 表格 1 将动态生成如下内容:

class BuyAppleForm(forms.Form):
   creditcard = forms.ChoiceField(widget = forms.Radioselect)
   type = forms.ChoiceField(widget = forms.RadioSelect)
   def __init__(self,*args, **kwargs):
        user = kwargs['user']
        del kwargs['user']

        super(BuyAppleForm, self).__init__(*args, **kwargs)

        credit_cards = get_credit_cards(user)
        self.fields['creditcard'].choices = [(card.id,str(card)) for card in credit_cards]

        apple_types= get_types_packages()
        self.fields['type'].choices = [(type.id,str(type)) for type in apple_types]

这将动态创建一个包含可用选项列表的表单。

我的第二种形式,实际上我不想输入。我只想显示一个确认屏幕,其中包含信用卡信息、苹果信息和金额(总额、税金、运费)。一旦用户点击确定,我希望苹果购买开始。

我能够通过在 kwargs 中传入 request.user 对象来实现单一表单方式。但是,使用 FormWizard,我无法解决这个问题。

我是否错误地处理了问题,FormWizard 不是正确的方法吗?如果是,Form__init__ 方法如何从 HTTP 请求中访问用户对象?

【问题讨论】:

【参考方案1】:

我没有使用它,但是对于您描述的情况,您可能想尝试FormPreview 而不是FormWizard。从文档来看,这听起来像是您所追求的。

【讨论】:

感谢您指出 FormPreview。但是,在我的情况下,部分问题是将额外的 **kwargs 值(request.user as 'user')传递给 Form 构造函数的 init (动态表单生成所需),而且我看不到 FormPreview 是如何实现的。 我不确定它是否有帮助(使用 FormPreview),但您可以在上面的代码中做的事情是不要将该代码放入 init 方法中。例如x = BuyAppleForm(),然后在显示之前执行 x.set_choices_for_user(request.user)。【参考方案2】:

当我试图弄清楚 FormWizard 时,我搜索了所有内容,发现大多数回复都只是说不要使用它。 FormPreview 可以正常工作,因为 OP 只对一级表单感兴趣,但问题仍然适用于如何使用 FormWizard。

尽管这个问题太老了,但我认为在这里回答很有价值,因为这个问题在很多网站上都被问到过,我没有看到对它的一致回应,也没有在文档中找到明确的解决方案。

我认为就 OPs 问题而言,覆盖 process_step 是要走的路。诀窍是在此方法中创建将从第一个表单接收数据的表单(或视图)。

我将此 form_setup 添加到我的 forms.py 作为实用程序包装器(想想构造函数):

def form_setup(**kwargs):
    def makeform(data, prefix=None, initial=None):
        form = FormLev2(data, prefix, initial)
        for k, v in kwargs.items():
            if k == 'some_list':
                form.fields['some_list'].choices = v
            ...
        return form
    return makeform

然后重写 process_step 如下:

def process_step(self, request, process, step):
    if step == 1
        if form.is_valid():  #form from step 1
            objs = Table.objects.filter(...) #based on last form 
            self.form_list[1] = form_setup(some_list=[(o.id,o.name) for o in objs])  #(*)
    ...

这样,您可以动态修改 form_list(*),即修改 FormWizard 实例中的 form_list,而不是修改表单定义本身。包装函数对于此功能至关重要,因为它返回一个函数,该函数将实例化一个新的 Form 对象,然后在 FormWizard 中使用该对象与下一个表单的数据一起调用,并允许您使用前一个表单中的数据.

编辑:Erik 的评论,并澄清最后一部分。

还要注意 process_step 会在 step n 之后被 step [0,n] 调用。

【讨论】:

for k, v in kwargs 行必须是 for k, v in kwargs.items()。除此之外,很好的解决方案:)【参考方案3】:

我不知道在 *** 上回答自己的问题是否可以接受,这是我对自己问题的解决方案。

首先,放弃 FormWizard。

我有一个表格。 两个视图:buy_applesbuy_apples_confirm

第一个视图只处理 GET。它打印出未绑定的表单,并带有一个转到第二个视图的 URL 的操作。

第二个视图检查是否存在名为“confirm”的 POST 参数。如果它不存在(因为第一次加载视图时不存在)它:

    将所有字段上的小部件调整为隐藏输入 写出提供订单摘要的模板。此模板还将一个名为“确认”的隐藏字段设置为 1(即使表单上不存在此字段)

当用户点击购买苹果时,表单被提交回来,buy_apples_confirm 视图被再次调用一次。这一次,出现了一个名为“confirm”的 POST 参数,因此我们实际处理了购买交易,用户得到了他的苹果。

我欢迎对这种方法或处理这种情况的更好方法提出任何批评。我是 Django 新手,发现有很多不同的方法可以解决问题。不过我想向最好的人学习。

【讨论】:

【参考方案4】:

感谢 krys 回答您自己的问题。帮了我,但我还是有一些意见。

FormPreview 不是要走的路,因为据我所知它不支持动态表单。它依赖于一个固定的表单类来从那里生成。但是我们在这里使用函数动态生成。也许 FormPreview 有一天会支持这个(或者已经支持了,我不知道怎么做)。

Krys 解决方案似乎与 FormPreview 一样。只有散列被遗漏了,所以用户可以更改隐藏字段中的数据,或者您是否再次检查它?如果你再次检查它,那将不会遵循 DRY,因为你重复了检查(好吧,可能是可重复使用的方法,所以只有很小的重复)。

我想知道的是,您如何调整小部件?您是使用新的小部件复制表单还是有办法动态更改它?

【讨论】:

【参考方案5】:

改变 call 方法以获取额外的参数怎么样?

类似的东西:http://d-w.me/blog/2010/3/18/15/

【讨论】:

以上是关于带有动态表单的 Django FormWizard的主要内容,如果未能解决你的问题,请参考以下文章

Django formwizard 与表单集和表单在同一页面上

带有模板的自定义 Django FormWizard 步骤

Django:在表单向导中为步骤动态设置表单集

django manytomany 字段使用 through 和 formwizard

Django FormWizard如何动态更改form_list

Django 的 FormWizard 中的空 ModelFormset