带有动态表单的 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_apples
和 buy_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 manytomany 字段使用 through 和 formwizard