使用 RadioSelect 小部件自定义 Django 表单

Posted

技术标签:

【中文标题】使用 RadioSelect 小部件自定义 Django 表单【英文标题】:Customizing Django forms with RadioSelect widget 【发布时间】:2012-07-18 08:38:57 【问题描述】:

所以我使用 jQuery UI 来skin the radio buttons,但我无法让 Django 以必须完成的方式呈现我的表单。

我需要这个结构:

<table>
    <tr>
        <td><label for="notify_new_friends">Notify when new friends join</label></td>
        <td class="radio">
            <input type="radio" name="notify_new_friends" id="notify_new_friends_immediately" value="1" checked="checked"/><label for="notify_new_friends_immediately">Immediately</label>
            <input type="radio" name="notify_new_friends" id="notify_new_friends_never" value="0"/><label for="notify_new_friends_never">Never</label>
        </td>
    </tr>
</table>

总结一下,我需要一个类(单选)中的单选按钮,它们有一个input 和一个label for

当我使用 profile_form.notify_new_friends 在模板中呈现表单时,我得到以下信息:

<ul>
    <li><label for="id_notify_new_friends_0"><input type="radio" id="id_notify_new_friends_0" value="0" name="notify_new_friends" /> Immediately</label></li>
    <li><label for="id_notify_new_friends_1"><input type="radio" id="id_notify_new_friends_1" value="1" name="notify_new_friends" /> Never</label></li>
</ul>

这正是我想要的,除了列表部分。所以我尝试循环遍历它,这给了我不同格式的标签:

% for item in profile_form.notify_new_friends %
     item 
% endfor %

这给了我:

<label><input type="radio" name="notify_new_friends" value="0" /> Immediately</label>
<label><input type="radio" name="notify_new_friends" value="1" /> Never</label>

所以这里的问题是它停止使用label for 并开始使用label 来包装它。

我也尝试过这样做,但是 labellabel_tag 不渲染任何东西。

 profile_form.notify_new_friends.0 
 profile_form.notify_new_friends.0.label_tag 
 profile_form.notify_new_friends.0.label 

那么有谁知道我该如何正确渲染它!?

仅供参考,这是我的 forms.py:

self.fields['notify_new_friends'] = forms.ChoiceField(label='Notify when new friends join', widget=forms.Radioselect, choices=NOTIFICATION_CHOICES)

【问题讨论】:

docs.djangoproject.com/en/dev/ref/forms/widgets/#radioselect - 你读过这个吗? @AlexanderA.Sosnovskiy 是的,但我错过了 choice_label 来获得标签标题,但不幸的是, radio.tag 没有为我提供 ID,因此我不能这样做 label for我需要做的。 字段 id 是自动生成的尝试, field.auto_id @ArgsKwargs 我可能误解了如何使用它,但不,这并没有给我任何渲染。 【参考方案1】:

在我的代码中,我发现将小部件从

forms.RadioSelect

forms.RadioSelect(attrs='id': 'value')

神奇地导致生成的tag 值包含id 属性和附加的项目索引。如果你使用

% for radio in form.foo %
    <li>
         radio 
    </li>
% endfor %

在表单中,您会得到一个包裹在 input 周围的 label。如果您想要更传统的input 后跟label,您需要这样做:

% for radio in form.value %
    <li>
         radio.tag 
        <label for="value_ forloop.counter0 "> radio.choice_label </label>
    </li>
% endfor %

【讨论】:

【参考方案2】:

不幸的是,这比应有的复杂得多,看来您至少需要覆盖 2 个类:RadioRendererRadioInput。以下内容应该可以帮助您入门,但您可能需要稍作调整。

首先创建一个自定义单选按钮输入小部件。我们重写 render 方法的唯一目的是摆脱 Django 强制执行的烦人结构 (&lt;label&gt;&lt;input /&gt;&lt;/label&gt;),而我们想要我们的 (&lt;label /&gt;&lt;input /&gt;):

class CustomRadioInput(RadioInput):
    def render(self, name=None, value=None, attrs=None, choices=()):
        name = name or self.name
        value = value or self.value
        attrs = attrs or self.attrs
        if 'id' in self.attrs:
            label_for = ' for="%s_%s"' % (self.attrs['id'], self.index)
        else:
            label_for = ''
            choice_label = conditional_escape(force_unicode(self.choice_label))
            return mark_safe(u'%s<label%s>%s</label>' % (self.tag(), label_for, choice_label))

现在我们需要覆盖 RadioRenderer 以便:

    强制它使用我们的自定义单选输入小部件 删除 &lt;li&gt; 包装每个输入字段和 &lt;ul&gt; 包装所有输入字段:

应该这样做:

class RadioCustomRenderer(RadioFieldRenderer):
    def __iter__(self):
        for i, choice in enumerate(self.choices):
            yield CustomRadioInput(self.name, self.value, self.attrs.copy(), choice, i)

    def __getitem__(self, idx):
        choice = self.choices[idx]
        return CustomRadioInput(self.name, self.value, self.attrs.copy(), choice, idx)

    def render(self):
        return mark_safe(u'%s' % u'\n'.join([u'%s' % force_unicode(w) for w in self]))

最后指示 Django 使用自定义渲染器

notify_new_friends = forms.ChoiceField(label='Notify when new friends join', widget=forms.RadioSelect(renderer=RadioCustomRenderer), choices=NOTIFICATION_CHOICES)

请记住:这会输出单选按钮以及包含&lt;td&gt;,因此您需要在模板中围绕它构建一个表格,如下所示:

<table>
  <tr>
    <td><label for="field.auto_id">field.label</label></td>
    <td> field.errors  field</td>
  </tr>
</table>

【讨论】:

我同意,这方面的文档缺少 imo。虽然我同意大多数 Django 文档,但使用 shell 的 html 表单示例并不是最好的方法。【参考方案3】:

如果有人偶然发现这个问题并且只想在没有 ul 的情况下呈现单选按钮:他们应该点击这个链接。

https://docs.djangoproject.com/en/3.1/ref/forms/widgets/#selector-widgets

以下示例。

% for radio in myform.beatles %
    <div class="myradio">
     radio 
    </div>
% endfor %

【讨论】:

终于找到了答案!谢谢。【参考方案4】:

由于这似乎不是一个好方法,我选择使用 jQuery 重新排列生成的代码。

// First remove the ul and li tags
$('.radio ul').contents().unwrap();
$('.radio li').contents().unwrap();
// Then move the input to outside of the label
$('.radio > label > input').each(function() 
    $(this).parent().before(this);
);
// Then apply the jQuery UI buttonset
$( ".radio" ).buttonset();

这使它从:

<ul>
    <li><label for="id_notify_new_friends_0"><input type="radio" id="id_notify_new_friends_0" value="0" name="notify_new_friends" /> Immediately</label></li>
    <li><label for="id_notify_new_friends_1"><input type="radio" id="id_notify_new_friends_1" value="1" name="notify_new_friends" /> Never</label></li>
</ul>

到:

<input type="radio" id="id_notify_new_friends_0" value="0" name="notify_new_friends" /><label for="id_notify_new_friends_0"> Immediately</label></li>
<input type="radio" id="id_notify_new_friends_1" value="1" name="notify_new_friends" /><label for="id_notify_new_friends_1"> Never</label></li>

我的 jQuery UI 样式工作正常。

【讨论】:

我认为如果有可能在没有 javascript 的情况下执行此操作,我们应该避免使用 js……尤其是在页面加载时 - 性能更差,SEO 更差。如果我们使用javascript进行操作,应该是动态操作,导致交互这样的原因。 我不认为简单地重新排列页面上相同内容的 JavaScript 可能会影响 SEO。除此之外,此解决方案可能在 Django 管理员中运行良好,其中 SEO 无关紧要,控制 HTML 输出将需要覆盖多个 Django 类,如 tnajdek 的回答中所述。【参考方案5】:
Try like this ,  I got it..

from django.forms.widgets import RadioFieldRenderer
from django.utils.encoding import force_unicode
from django.utils.safestring import mark_safe


class RadioCustomRenderer( RadioFieldRenderer ):
    def render( self ):
        return  mark_safe(u'%s' % u'\n'.join([u'%s'  % force_unicode(w) for w in self]))

in form

widgets = 
            'validity': forms.RadioSelect(renderer=RadioCustomRenderer),
        

【讨论】:

以上是关于使用 RadioSelect 小部件自定义 Django 表单的主要内容,如果未能解决你的问题,请参考以下文章

使用包含 RadioSelect 小部件的 ModelChoiceField 在 ModelForm 的查询集中获取 request.user

如何在 Flutter 小部件中测试回调函数

自定义 radioselect 默认渲染器

如何将 CSS 类添加到 django RadioSelect 标签?

将 CSS 类添加到 django RadioSelect 标签

如何在不导入自定义小部件类包的情况下使用自定义小部件和 uic.loadUi?