具有动态表单的 Django FormWizard

发布于 2024-07-09 11:25:27 字数 962 浏览 8 评论 0原文

我想实现一个简单的两部分 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 请求访问用户对象?

I want to implement a simple 2 part FormWizard.
Form 1 will by dynamically generated something like this:

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]

This will dynamically create a form with lists of available choices.

My second form, I actually want no input. I just want to display a confirmation screen containing the credit card info, apple info, and money amounts (total, tax, shipping). Once user clicks OK, I want the apple purchase to commence.

I was able to implement the single form way by passing in the request.user object in the kwargs. However, with the FormWizard, I cannot figure this out.

Am I approaching the problem wrong and is the FormWizard not the proper way to do this? If it is, how can the Form __init__ method access the user object from the HTTP request?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

暖阳 2024-07-16 11:25:28

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

首先,放弃 FormWizard。

我有一种形式。
两个视图:buy_applesbuy_apples_confirm

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

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

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

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

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

I don't know if answering one's own question is an acceptable behaviour on StackOverflow, here is my solution to my own problem.

First, ditch FormWizard.

I have one form.
Two views: buy_apples and buy_apples_confirm

First view only handles GET. It prints out the unbound form, with an action to go to the URL of the second view.

The second view checks for the presence of a POST parameter named "confirm". If it is not present (as it is not when the view is loaded the first time) it:

  1. Adjusts the widget on all the fields to be HiddenInput
  2. Writes out template which gives an order summary. This template also sets a hidden field called "confirm" to 1 (even though this field does not exist on the Form)

When the user clicks to buy the apples, the form is submitted back and the buy_apples_confirm view is invoked one more time. This time, a POST parameter called "confirm" is present, so we actually process the purchase transaction and the user gets his apples.

I welcome any critiques on this method or better ways of handling the situation. I am new to Django and find that there are many different ways of approaching a problem. I want to learn from the best though.

青春如此纠结 2024-07-16 11:25:28

感谢克里斯回答您自己的问题。 对我有帮助,但我仍然得到了一些评论。

FormPreview 不是可行的方法,因为据我所知它不支持动态表单。 它依赖于一个固定的表单类来从那里生成。 但我们在这里用一个函数动态生成。 也许有一天 FormPreview 会支持这一点(或者已经支持了,但我不知道如何支持)。

Krys 解决方案似乎与 FormPreview 的功能相同。 仅留下哈希值,因此用户可以更改隐藏字段中的数据,或者您是否再次检查? 如果你再次检查它,那就不会遵循 DRY,因为你重复了检查(好吧,可能是在可重用的方法中,所以只有很小的重复)。

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

Thank you krys for answering to your own question. Helped me, but I still got some remarks.

FormPreview is not the way to go since it as far as I know does not support dynamic forms. It relies on a fixed form class to generate the from from there. But we are generating dynamically here with a function. Maybe FormPreview will support this one day (or already does and I dont know how).

Krys solution seems to do the same as FormPreview. Only the hash is left out, so user may change data in the hidden fields or do you check it again?. If you check it again, that would not be following DRY because you duplicate the check (okay, could be in a reusable method, so only tiny repetition).

What I was wondering, how do you adjust the widget? Do you duplicate the form with the new widgets or is there a way to change that dynamically?

大姐,你呐 2024-07-16 11:25:28

如何更改 call 方法以获取额外参数?

类似的东西: http://dw.me/blog/2010/3/ 18/15/

How about changing the call method to take an extra parameter?

something similar to this: http://d-w.me/blog/2010/3/18/15/

薔薇婲 2024-07-16 11:25:27

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

I haven't used it, but for the situation you describe, it seems like you may want to try the FormPreview instead of the FormWizard. From the documentation it sounds like what you're after.

放赐 2024-07-16 11:25:27

当我试图找出 FormWizard 时,我进行了全面搜索,发现大多数回复都只是说不要使用它。 FormPreview 可以正常工作,因为 OP 只对一级表单感兴趣,但问题在如何使用 FormWizard 方面仍然有效。

尽管这个问题已经很老了,但我认为在这里回答是有价值的,因为这个问题在很多网站上都被问到,而且我没有看到对此有凝聚力的回应,也没有在文档中找到明确的解决方案。

我认为就操作问题而言,重写 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 中使用该对象以使用下一个表单的数据进行调用,并允许您使用前一个表单中的数据。

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

另请注意,process_step 将在步骤 n 之后使用步骤 [0,n] 进行调用。

When I was trying to figure out FormWizard, I searched all over and found responses such as most of these that just say don't use it. FormPreview would work fine since OP is only interested in a one-level form, but the question is still valid in how to use FormWizard.

Even though this question is so old, I think it is valuable to answer here because this question is asked on so many sites and I see no cohesive response to it, nor a clear solution in the docs.

I think in terms of the OPs question, overriding process_step is the way to go. The trick is in creating the form (or view) within this method that will receive the data from the first form.

I added this form_setup to my forms.py as a utility wrapper (think constructor):

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

Then override process_step as follows:

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])  #(*)
    ...

That way, you are able to dynamically modify form_list(*), in the sense that you modify the form_list in the FormWizard instance, rather than the form definitions themselves. The wrapper function is essential for this functionality, as it returns a function that will instantiate a new Form object, which is then used within FormWizard to be called with the data for the next form, and allows you to use the data from the previous one.

Edit: for Erik's comment, and to clarify the last part.

Also note that process_step will be called with step [0,n] after step n.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文