如何在模板中获取ModelChoiceField实例

时间:2012-04-24 15:05:42

标签: python django django-forms

我有一个ModelForm,它包含一个使用RadioSelect小部件的ModelChoiceField。

class MyAForm(forms.ModelForm):
    one_property = models.ModelChoiceField(
        widget=forms.RadioSelect,
        queryset=MyBModel.objects.filter(visible=True),
        empty_label=None)
    class Meta:
        model = MyAModel

我想在单选按钮旁边显示MyBModel上的属性。我会在ModelChoiceField的子类上覆盖label_from_instance但是这不允许我做我想要的,因为我希望单选按钮出现在一个表中,每个选择项都有一行。

所以在我的模板中的某个地方,我想要像......

{% for field in form.visible_fields %}
    {% if field.name == "one_property" %}
    <table>
        {% for choice in field.choices %}
            <tr>
                <td><input value="{{choice.id}}" type="radio" name="one_property" />{{choice.description}}</td>
                <td><img src="{{choice.img_url}}" /></td>
            </tr>
        {% endfor %}
    </table>
    {% endif %}
{% endfor %}

不幸的是,field.choices返回对象id和标签的元组,而不是查询集中的实例。

是否有一种简单的方法可以获取ModelChoiceField在模板中使用的选项实例?

3 个答案:

答案 0 :(得分:12)

在深入研究ModelChoiceField的django源代码后,我发现它有一个属性“queryset”。

我能够使用像...这样的东西。

{% for field in form.visible_fields %}
    {% if field.name == "one_property" %}
    <table>
        {% for choice in field.queryset %}
            <tr>
                <td><input value="{{choice.id}}" type="radio" name="one_property" />{{choice.description}}</td>
                <td><img src="{{choice.img_url}}" /></td>
            </tr>
        {% endfor %}
    </table>
    {% endif %}
{% endfor %}

答案 1 :(得分:4)

我想做一些与OP的问题几乎完全相同的事情(表格和所有内容),Django缺乏合作也同样感到沮丧,同样最终深入研究了我自己的实现。我提出的内容与接受的答案略有不同,我更喜欢它,因为我在模板中使用了一个简单的{{ form.as_table }},并且不想不必要地遍历visible_fields或者在我的模板中硬编码一个单选按钮,它看起来与Django当前的实现类似(可能会改变)。这就是我做的事情:

RadioInput和RadioFieldRenderer

Django的RadioSelect窗口小部件使用RadioFieldRenderer生成generator RadioInputs,用于执行渲染单选按钮的实际工作。 RadioSelect似乎有一个未记录的功能,您可以在其中传递与此默认值不同的渲染器,因此您可以将这两个渲染器子类化以获得OP所需的内容。

from django import forms
from django.utils.safestring import mark_safe

class CustomTableRadioInput(forms.widgets.RadioInput):

    # We can override the render method to display our table rows
    def render(self, *args, **kwargs):
        # default_html will hold the normally rendered radio button
        # which we can then use somewhere in our table
        default_html = super(CustomTableRadioInput, self).render(*args, **kwargs)
        # Do whatever you want to the input, then return it, remembering to use
        # either django.utils.safestring.mark_safe or django.utils.html.format_html
        # ...
        return mark_safe(new_html)

class CustomTableFieldRenderer(forms.widget.RadioFieldRenderer):
    # Here we just need to override the two methods that yield RadioInputs
    # and make them yield our custom subclass instead
    def __iter__(self):
        for i, choice in enumerate(self.choices):
            yield CustomTableRadioInput(self.name, self.value,
                                          self.attrs.copy(), choice, i)

    def __getitem__(self, idx):
        choice = self.choices[idx] # Let the IndexError propogate
        return CustomTableRadioInput(self.name, self.value,
                                       self.attrs.copy(), choice, idx)

完成后,我们只需要告诉RadioSelect小部件,只要我们在表单代码中的某处调用它,就可以使用我们的自定义渲染器:

...
radio = forms.ChoiceField(widget=forms.RadioSelect(renderer=CustomTableFieldRenderer),
                          choices=...)
...

就是这样!

请注意,要在模板中使用它,您可能希望循环遍历字段而不是直接调用它,即:

<table>
  <tbody>
  {% for tr in form.radio %}
    <tr>{{ tr }}</tr>
  {% endfor %}
  </tbody>
</table>

而不是:

<table>
  <tbody>{{ form.radio }}</tbody>
</table>

如果您执行后者,它将尝试将您的表格元素包装在<ul><li>...</li></ul>

答案 2 :(得分:2)

通常你不需要实际的对象,而是它的再现。

考虑以下代码:

class LabelledHiddenWidget(forms.HiddenInput):

    def __init__(self, get_object, *args, **kwargs):
        super(LabelledHiddenWidget, self).__init__(*args, **kwargs)
        self.get_object = get_object

    def render(self, name, value, attrs=None):
        s = super(LabelledHiddenWidget, self).render(name, value, attrs)
        if value:
            s += SafeUnicode("<span>%s</span>" % self.get_object(value))
        return s

然后你可以像这样使用它:

class SomeForm(forms.Form):
    object = forms.ModelChoiceField(
         SomeModel.objects.all(), 
         widget=LabelledHiddenWidget(get_object=lambda id: get_object_or_404(SomeModel, id=id)))

然后在模板代码中,{{ form.object }}将输出一个带有对象id的隐藏字段,并与某个标签连接。当然,你的SomeModel应该实现__unicode__或其他一些返回一个漂亮的,可读的标签的方法。