14

我有一个 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 上有一些我想在单选按钮旁边显示的属性。我会覆盖label_from_instanceModelChoiceField 的子类,但这不允许我做我想做的事,因为我希望单选按钮出现在每个选择项都有一行的表中。

所以在我的模板中的某个地方我想要类似...

{% 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 的选择实例?

4

3 回答 3

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 %}
于 2012-04-24T15:36:26.983 回答
4

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

RadioInput 和 RadioFieldRenderer

Django 的RadioSelect小部件用于生成RadioFieldRenderer生成器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>.

于 2013-09-13T15:03:48.720 回答
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__或返回一个漂亮的、人类可读的标签的其他方法。

于 2013-04-30T14:14:03.113 回答