django形成无线电输入布局

时间:2011-06-02 17:56:16

标签: python django django-forms

解决这个问题的“djangoy”方法是什么:

在我的表单类中,我有一个forms.ChoiceField,它的widget是一个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 个答案:

答案 0 :(得分:5)

如果我正确理解您的问题,您可以访问模板中的选择元组:

<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>

答案 1 :(得分:4)

安东的回答有效,并且在那里有一段时间是一个不错的答案 - 但不幸的是它变得不可维护。因此,从附加到a diffdjango ticket #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 %}直接访问无线电输入。更容易照顾!

答案 2 :(得分: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

答案 3 :(得分:3)

我会通过继承RadioFieldRenderer并将其附加到自定义小部件来完成此操作:

# forms.py
from django import forms
from django.forms.widgets import RadioSelect, RadioFieldRenderer
from django.template.loader import render_to_string
from myapp.models import RatherComplicatedModel


class MyRadioFieldRenderer(RadioFieldRenderer):
    def render(self):
        return render_to_string(
            'my_radio_widget.html',
                    {'field': self})


class MyRadioSelect(RadioSelect):
    renderer = MyRadioFieldRenderer


class RatherComplicatedForm(forms.ModelForm):
    RADIO_CHOICES = (
        ('none', "No Textbox"),
        ('one', "One Textbox: "),
    )
    rad = forms.ChoiceField(widget=MyRadioSelect(),choices=RADIO_CHOICES)

    class Meta:
        model = RatherComplicatedModel

然后是模板:

#my_radio_widget.html
<ul>
    {% for choice in field %}
        <li>
            <label for="id_{{ field.name }}_{{ forloop.counter0 }}">
                <input type="radio"
                       name="{{ field.name }}"
                       value="{{ choice.choice_value }}"
                       id="id_{{ field.name }}_{{ forloop.counter0 }}"
                       {% if field.value == choice.choice_value %}
                           checked='checked'
                       {% endif %}/>
                {{ choice.choice_label }}
            </label>
        </li>
    {% endfor %}
</ul>