【问题标题】:django form radio input layoutdjango表单收音机输入布局
【发布时间】:2019-02-04 16:37:09
【问题描述】:

解决这个问题的“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)

【问题讨论】:

  • 你能告诉我们你的表单类的代码吗?

标签: python django django-forms


【解决方案1】:

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

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

【讨论】:

  • 有没有办法自动找出 BoundField 的 id 和 name?我承认,我只是有点害怕把硬编码的东西放在那里,以防我改名等。
  • 我明白了。我认为,是的,它是:使用{{ field.html_name }}。并且用于输入元素的id 属性会自动生成为id_&lt;field_name&gt;。 (另见docs.djangoproject.com/en/dev/topics/forms/…
  • 好吧,我终于有时间申请这个了……在我接受之前还有一个问题 - form.rad 没有 choices - form.rad.field 有。
  • 是的,我总是忘记实际字段(在这种情况下为ChoiceField)是BoundField 实例上的一个属性(您在模板中迭代这些属性)。修正了答案。
【解决方案2】:

Anton 的答案奏效了,并且在那里有一段时间是一个不错的答案 - 但不幸的是,它变得无法维护。所以,从a diff 附加到django 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 %} 直接访问无线电输入。更容易照顾!

【讨论】:

    【解决方案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
    

    【讨论】:

    • 对不起 - 我想我不够具体。我希望用户能够在bar 框中输入任意数字 - 但只有在他们选择了第二个单选按钮时才会验证该条目。如果他们选择第一个单选按钮(不显示 bar 框),则不会验证 bar。
    【解决方案4】:

    我会通过继承 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>
    

    【讨论】:

    • RadioFieldRenderer 现已弃用
    猜你喜欢
    • 1970-01-01
    • 2022-01-14
    • 2013-04-26
    • 1970-01-01
    • 2019-03-07
    • 2017-11-20
    • 1970-01-01
    • 2019-12-25
    相关资源
    最近更新 更多