【问题标题】:How to render django form differently based on what user selects?如何根据用户选择以不同方式呈现 django 表单?
【发布时间】:2014-03-25 12:31:06
【问题描述】:

我有这样的模型和表格:

class MyModel(models.Model):
    param = models.CharField()
    param1 = models.CharField()
    param2 = models.CharField()

class MyForm(forms.ModelForm):
    class Meta:
        model = MyModel
        fields = ('param', 'param1', 'param2')

然后我有一个具有不同值的下拉菜单,根据选择的值,我隐藏和显示 MyForm 的字段。现在我必须更进一步,如果用户从下拉列表中选择某个值,则将 param2 呈现为 CheckboxInput 小部件,但在其他情况下,它应该是标准文本字段。那我该怎么做呢?

【问题讨论】:

  • 你试过用Javascript吗?
  • 我不想在 Javascript 中破解类似的东西,因为 Django 表单的全部意义在于它们是为您呈现的。我更多地考虑在 MyModel 上创建多个表单,然后使用 Javascript 显示/隐藏它们。但是,我将如何控制我实际从哪个表单获取数据?
  • Django 可以为您呈现表单,但它没有在客户端更改字段类型的内置机制。在这种情况下,JavaScript 确实是您的最佳选择,imo。

标签: django


【解决方案1】:

我知道这篇文章已经快一年了,但我花了好几个小时才找到与这个主题相关的帖子(这是我找到的唯一一个,在提交我自己的问题时出现相关),所以我觉得有必要分享我的解决方案。

如果下拉菜单中的选项与存储在另一个模型中的值匹配,我想要一个表单,该表单将显示并需要一个文本字段。我在两个模型之间有一个 foreignKey 关系,我将 Model1 的一个实例传递到 Model2 的 ModelForm 中。如果为 Model2 中的变量选择的值与 Model1 中已设置的变量匹配,我想显示并需要一个文本字段。这基本上是一个“选择其他,然后输入您自己的描述”的场景。

我不希望页面重新加载(我试图在移动和桌面浏览器中以最少的延迟/重新加载并为两者使用相同的代码),所以我不能使用提到的多个表单加载在视图选项中。当我意识到我在考虑这个问题时,我开始尝试按照上面的建议使用 AJAX。

答案是在表单中使用 JS 和 clean 方法。我在我的 Model2Form 中添加了一个不在 Model2 中的非必填字段 (field1)。然后,我使用 jQuery 隐藏它,并且仅在另一个字段 (field2) 的值与 Model1 中的变量值匹配时才显示它(使用 jQuery)。为了完成这项工作,我确实决定在我的模板中隐藏一个带有变量 pk 的 ,这样我就可以使用 jQuery 轻松获取它。这个 jQuery 非常适合正确地隐藏和显示字段,因此用户可以选择“其他”值,然后决定选择不同的值(并无休止地来回走动)。

然后,我在我的 Model2Form 中为 field1 使用了一个干净的方法,如果当 field2 中的值与我的 Model1 变量匹配时没有输入任何值,则会引发 ValidationError。我通过在我的 __ init __ 方法中使用“self.other = Model1.variable”访问该变量,然后在 clean_field1 方法中引用它。

我希望能够在不必使用 JS 隐藏和显示字段的情况下完成此操作,但我认为使用视图或 ajax 这样做的唯一解决方案会导致我不想要的延迟/重新加载。此外,我喜欢我使用的方法的总体简单性,而不是必须弄清楚如何通过 HTTPRequest 来回传递部分表单。

更新:
在我的情况下,我正在为丢失和找到的物品创建条目,如果找到物品的位置不是提​​供的选项,那么我想显示一个文本框供用户输入位置。我创建了一个设置为“其他”位置的位置对象,然后在该对象被选为“找到”位置时显示文本框。

在 forms.py 中,我添加了一个额外的 CharField 并使用 clean 方法检查该字段是否为必填项,如果未填写则抛出 ValidationError:

class Model2Form(forms.ModelForm):
    def __init__(self, Model1, *args, **kwargs):
        self.other = Model1.otherLocation
        super(Model2Form, self).__init__(*args, **kwargs)
        ...

    otherLocation = forms.CharField(
        label="Location Description",
        max_length=255,
        required=False
    )

    def clean_otherLocation(self):
        if self.cleaned_data['locationFound'] == self.other and not self.cleaned_data['otherLocation']:
            raise ValidationError("Must describe the location.")
        return self.cleaned_data['otherLocation']

然后在我的 JavaScript 中,我检查了“找到”位置的值是否是“其他”位置(我在 html 页面上的隐藏跨度中拥有的值)。然后,我根据需要在文本框的父元素上使用了 .show() 和 .hide():

$("#id_locationFound").change( function(){
    if ($("#id_locationFound").val() == $("#otherLocation").attr("value")){ //if matches "other" location, display textbox; otherwise, hide textbox
        $("#id_otherLocation").parent().show();
    }else
        $("#id_otherLocation").parent().hide();
});

【讨论】:

  • 我添加了一些代码 sn-ps 来说明我在说什么,抱歉原文太抽象了,没有示例代码。
【解决方案2】:

您最好的猜测是当您从下拉菜单中选择某些内容时触发“POST”请求。

“POST”的值必须与您用来确定要输出哪个字段的值相对应。

现在你实际上需要两种形式:

class MyBaseForm(forms.ModelForm):
    class Meta:
        model = MyModel
        fields = ('param', 'param1', 'param2')

class MyDropDownForm(MyBaseForm):
    class Meta:
        widgets = {
            'param2': Select(attrs={...}),
        }

如您所见,DropDownForm 是从 MyBaseForm 派生的,以确保它具有所有相同的属性。但是我们修改了其中一个字段的小部件。

现在您可以更新您的视图。请注意,这是未经测试的 Python + 伪代码

views.py

def myFormView(request):
  if request.method == 'POST': # If the form has been submitted...    
    form = MyBaseForm(request.POST)

    #submit button has not been pressed, so the dropdown has triggered the submission. 
    #Hence we won't safe the form, but reload it            
    if 'my_real_submitbotton' not in form.data:     

      if 'param1' == "Dropdown":
         form = MyDropDownForm(request.POST)

    else:
      #do your normal form saving procedure
  else:
    form = ContactForm() # An unbound form

  return render(request, 'yourTemplate.html', {
    'form': form,
  })

此机制执行以下操作: 提交表单时,它会检查您是否按下了“提交”按钮或使用下拉 onChange 来触发提交。我的解决方案不包含使用 onChange 触发提交所需的 javascript 代码。我只是想提供一种解决方法。 要在 form.data 构造中使用“my_real_submitbutton”,您需要为提交按钮命名:

<input type="submit" name="my_real_submitbutton" value="Submit" />

当然,您可以选择任何字符串作为名称。 :-)

如果通过您的下拉字段提交,您必须检查在此下拉菜单中选择了哪个值。如果此值满足您要返回下拉菜单的条件,则创建 DropDownForm(request.POST) 的实例,否则您可以保留所有内容并重新渲染模板。

不利的一面是,这会刷新您的页面。 从好的方面来说,它将保留所有已输入的字段值。所以这里没有伤害。

如果您想避免页面刷新,您可以保留我提出的想法,但您需要通过 AJAX 呈现新表单。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-05-29
    • 1970-01-01
    • 1970-01-01
    • 2022-07-08
    • 1970-01-01
    • 2021-12-19
    • 1970-01-01
    相关资源
    最近更新 更多