Django表单单选输入布局

7 投票
4 回答
13165 浏览
提问于 2025-04-16 18:51

“djangoy”的方式来处理这个问题是什么呢:

在我的表单类中,有一个 forms.ChoiceField,它的控件是 forms.RadioSelect,其中一个选项需要和一个文本输入框一起显示(这个文本输入框也是表单中的一个字段)。我使用自定义验证来忽略当没有选择这个单选项时的文本框。当渲染出来时,我希望它看起来像下面这样:

<ul>
<li><label for="id_rad_0"><input type="radio" id="id_rad_0" value="none" name="rad" /> No Textbox</label></li>
<li><label for="id_rad_1"><input type="radio" id="id_rad_1" value="one" name="rad" /> One Textbox: <input type="text" name="bar" id="id_bar" /></label></li>
</ul>

但是,我不能简单地在我的模板中实现这个,因为单选项的选择没有被暴露出来。我找不到一种方法来做到这一点,而不把我的表单和模板紧密耦合,或者把所有的展示逻辑放在表单类里。解决这个问题的正确方法是什么呢?

编辑

我意识到上面的问题可能比较冷门,但我不太确定我还能提供什么信息来激励别人帮助我。我在后端编程方面比在网页设计上要强很多,而且我一个人做这个项目,所以也许是我缺乏相关知识——我描述的这种情况算不算设计不当?我是否应该换一种方式来设计这个?我真的很欢迎任何建议,帮助我突破这个瓶颈。

编辑 2

应要求,当前的代码,简化以保持清晰,名字已更改以保护无辜:

# forms.py
from myapp.models import RatherComplicatedModel
from django import forms

class RatherComplicatedForm(forms.ModelForm):
    #various and sundry code...
    RADIO_CHOICES = (
        ('none', "No Textbox"),
        ('one', "One Textbox: "),
    )
    # although I've abbreviated the model, 'rad' does not appear in the model;
    # it merely provides input to the un-provided clean function
    rad = forms.ChoiceField(widget=forms.RadioSelect(),choices=RADIO_CHOICES)

    class Meta:
        model = RatherComplicatedModel

-

# models.py
from django.db import models

class RatherComplicatedModel(models.Model):
    #some other stuff...
    bar = models.IntegerField(blank=True,null=True)

4 个回答

3

选择项应该放在模型里面:

class RatherComplicatedModel(models.Model):
    BAR_CHOICES = (
        (0, "No Textbox"),
        (1, "One Textbox: "),
    )
    #some other stuff...
    bar = models.IntegerField(blank=True, null=True, choices=BAR_CHOICES)

然后只需要:

class RatherComplicatedForm(forms.ModelForm):
    #various and sundry code...
    bar = forms.ChoiceField(widget=forms.RadioSelect(), 
                 choices=RatherComplicatedModel.BAR_CHOICES)
    class Meta:
        model = RatherComplicatedModel
6

如果我理解你的问题没错的话,你可以在模板中访问选择的元组:

<ul>
    {# Assuming {{ field }} here is {{ form.rad }} #}
    {% for choice in field.field.choices %}
    <li>
        <label for="id_{{ field.html_name }}_{{ forloop.counter0 }}">
            <input type="radio"
                id="id_{{ field.html_name }}_{{ forloop.counter0 }}"
                value="{{ choice.0 }}"
                name="{{ field.html_name }}" />
            {{ choice.1 }}
            {% if choice.0 == 'one' %}
                {# Necessary field here #}
                {{ form.bar }}
            {% else %}
                No Textbox
            {% endif %}
        </label>
    </li>
    {% endfor %}
</ul>
4

Anton的回答曾经很好用,但后来变得难以维护。所以,我参考了一个差异文件,这个文件是django的第9230号问题附上的,我对django.forms.forms.BoundField进行了修改。

from django import forms

def MonkeyPatchDjangoFormsBoundField():
    def prepare_widget_render(self, widget=None, attrs=None, only_initial=False):
        """
        Prepare the data needed for the widget rendering.
        """
        if not widget:
            widget = self.field.widget

        attrs = attrs or {}
        auto_id = self.auto_id
        if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
            if not only_initial:
                attrs['id'] = auto_id
            else:
                attrs['id'] = self.html_initial_id

        if not only_initial:
            name = self.html_name
        else:
            name = self.html_initial_name

        return widget, name, attrs

    def as_widget(self, widget=None, attrs=None, only_initial=False):
        """
        Renders the field by rendering the passed widget, adding any HTML
        attributes passed as attrs.  If no widget is specified, then the
        field's default widget will be used.
        """
        widget, name, attrs = self.prepare_widget_render(widget, attrs, only_initial)
        return widget.render(name, self.value(), attrs=attrs)

    def __iter__(self):
        """
        Check if current widget has a renderer and iterate renderer.
        """
        widget, name, attrs = self.prepare_widget_render()
        if not hasattr(widget, 'get_renderer'):
            raise Exception, "Can not iterate over widget '%s'" % widget.__class__.__name__
        renderer = widget.get_renderer(name, self.value(), attrs=attrs)
        for entry in renderer:
            yield entry

    def __getitem__(self,idx):
        """
        Tries to use current widget's renderer, and then check attribute.
        """
        widget, name, attrs = self.prepare_widget_render()
        try:
            renderer = widget.get_renderer(name, self.value(), attrs=attrs)
            return renderer[idx]
        except Exception:
            return getattr(self,idx)

    forms.forms.BoundField.prepare_widget_render = prepare_widget_render
    forms.forms.BoundField.as_widget = as_widget
    forms.forms.BoundField.__iter__ = __iter__
    forms.forms.BoundField.__getitem__ = __getitem__

这样一来,我就可以直接访问单选按钮的输入了,可以用{{ form.field.0.tag }}来获取,或者通过循环来获取 - {% for radio in form.field %} {{ radio.tag }} {% endfor %}。这样处理起来简单多了!

撰写回答