如何使用Django将复选框转换为单选按钮,同时保留复选框的逻辑并在表单中使用它们?

webghufk  于 2023-07-01  发布在  Go
关注(0)|答案(1)|浏览(125)

我想知道如何在Django上定制一个函数,让我可以将复选框转换为单选按钮,同时保留复选框的逻辑。让我解释一下:我在Django上创建了一个表单,但是我的表单有几个字段,这些字段包含了不同行为的复选框。但在大多数情况下,可以在这些复选框上观察到的功能是:

  • 能够选中和取消选中一个框(正常功能)
  • 一组复选框中的两个复选框不能同时选中(复选框的唯一性)
  • 如果复选框封装了一个字段,则选中该复选框将显示隐藏的字段,而取消选中该复选框或选中另一个复选框将自动隐藏由前一个复选框封装的字段。但是我在Django文档中没有找到任何资源可以帮助我完成这项任务,特别是当我试图用JavaScript/jQuery实现客户端逻辑时,当我在网页上提交表单时,它在我的复选框中生成验证错误,这意味着在客户端实现的逻辑在服务器端没有找到匹配,所以我有点不知所措。我已经在这段代码上工作了一段时间,但几乎没有任何进展。

这段代码的最终结果应该允许我在我的表单中管理这个示例案例:字段标签:带有链接到发热字段的复选框的发热响应选项:是、否、DK(不知道)如果用户勾选“是”框,则会显示一条隐藏消息,询问症状发生的日期。如果用户取消选中“是”框或勾选其他两个框中的一个,即否或DK,这将自动隐藏由“是”选项封装的隐藏字段。知道在最后提交表单时不应该在复选框上生成验证错误。
我在下面创建了这个Django代码:

from django import forms

class CustomCheckbox(forms.CheckboxInput):
    def __init__(self, choices=None, hidden_fields_mapping=None, *args, **kwargs):
        self.choices = choices
        self.hidden_fields_mapping = hidden_fields_mapping
        super().__init__(*args, **kwargs)

    def render(self, name, value, attrs=None, renderer=None):
        attrs['data-group'] = name  # Ajout de l'attribut data-group
        html = super().render(name, value, attrs, renderer)
        html += f"<script>initCustomCheckbox('{attrs['id']}', {self.hidden_fields_mapping})</script>"
        return html

这个CustomCheckbox类通过使用自定义HTML属性和JavaScript来自定义复选框的外观和行为,为复选框添加了额外的功能。但这对我没有帮助,因为它不起作用。如果当我在带有复选框的表单字段中调用这个类时,它可以根据上游定义的具体内容来处理它,我会很高兴的。

n3ipq98p

n3ipq98p1#

是的,你可以这样做。在客户端需要做很多工作(对我来说是这样,因为我对JS/JQuery或DOM导航不太熟悉)。
如果您想要的是一些常见的选项,如单选按钮和“其他”按钮来显示TextInput,那么下面将为您做这件事。
如果这不是你想要的,我希望这是一个想法的来源...
以一种形式:

field = forms.CharField( ...  initial='?', ...
    widget =  ChoicesWithOtherWidget(
            attrs={ 'data-radio':'?__Specify',
            '   data-radio-labels':'Unspecified__Specify',  },
        ),

将显示为一个标签为“未指定”的单选按钮,它将返回一个问号,以及一个标签为“指定”的单选按钮。如果单击该控件,则会出现一个常规CharField小部件,并返回您在其中键入的任何内容。您可以有多个按钮,如果值也适合作为标签,则可以省略标签。这将显示四个按钮,分别标记为并返回100、200、381、525,第五个按钮用于显示TextInput框

field = forms.IntegerField( ...
    widget = ChoicesWithOtherWidget(
            attrs={ 'data-radio':'100__200__381__525__Other',  },

小部件代码(以及用于测试的表单和视图)

class ChoicesWithOtherWidget( forms.TextInput):
    """ creates radiobuttons for common choices but can input anything through "other"

    The radiobutton choices are supplied as "data-radio" attr on the TextInput widget, and
    their labels (which default to the choice) as  the "data-radio-labels" attr

    Its all done with Jquery. See the media file for more info
    """
    class Media:
        js = ( 'utils/choices_with_other_widget.js', )
        css = { 'all': ( 'utils/choices_with_other_widget.css',  )} # .inline-choices-with-other

    def __init__(self, attrs=None):
        super().__init__( attrs)
        self.attrs['class'] = ' '.join([ 'with-radio', self.attrs.get('class', '') ])

# testing the above needs a full JS browser. Easier just to make sure this works...

from django.views.generic import FormView

class ChoicesWithOtherTestForm( forms.Form):
    foo = forms.CharField(
        widget = ChoicesWithOtherWidget(
            attrs={
                #'class':'with-radio',  # built into widget
                'data-radio':'foo__bar__other',
                'data-radio-labels': 'foolish__barfacious',
        }),
    )

class ChoicesWithOtherTestView( FormView):
    form_class = ChoicesWithOtherTestForm
    template_name = 'jobs/simple_form.html'
    title='Test View'
    def form_valid( self, form):
        #DEBUG( self, form.cleaned_data)
        return HttpResponseRedirect(
            reverse( 'wafers:ok') + f"?foo={form.cleaned_data['foo']}" # easy to test
        )

utils/choices_with_other_widget.js(它在你的MEDIA_ROOT中,目前在我的项目中是/static

$(document).ready( function(){

// this converts a simple Django TextInput (CharField etc) into a choices + other
// inputter if it has attr "with-radio".
//
// The radiobutton choices are supplied as data-radio attr on the TextInput widget, and
// their labels (which default to the choice) as  the data-radio-labels attr
//
// tested that this DOM navigation works with form.as_table, _as_p and _as_ul.
//
// this was a lot more work than I expected!

$('input.with-radio').each( function(){
    var data = $(this).attr('data-radio').split('__') ;
    var labs = $(this).attr('data-radio-labels')
    var labels = ( labs === undefined ) ? [] : labs.split('__');
    var last_data = data[ data.length-1 ];
    var where = this;               // the input element
    $(where).removeAttr("required");  // make not required
    var id = $(this).attr('id') ;
    var name = $(this).attr('name')  ;
    //console.log( `data=${data} id=${id} last_data=${last_data}` )

    $.each( data, function(i, opt){
        var label = (labels[i] === undefined ) ? opt : labels[i] ;

        var button = $(
            `<div class="inline-choices-with-other ${name}-radio-inline">
            <label for="${id}_x${i}" > ${label}&nbsp;</label>
            <input type="radio" class="radio-2"
                id="${id}_x${i}" name="${name}" value="${opt}" />
            </div>`
            );
        //button.css('min-width', '100px'); //better in style sheet for the form
                                          // but this works nicely to avoid irregular spacing
        var new_button = $(where).before(button)

    });

    var radio_buttons = $(where).siblings(`div.${name}-radio-inline`).find('input');
    var last_button = $(radio_buttons).last();

    $(radio_buttons).slice(0,-1).each( function(){  // all but the last
        $(this).on('click', function(){
            $(where).hide();
            $(where).removeAttr("required");

            //needed clicking twice to fully un-select the "other" box
            // next line and later test  prevents a possible infinite recursion

            var trigger_needed = ( $(where).attr('name') == name  );

            $(where).attr( 'name', name+'___');    // hidden input submit name changed
            $(last_button).attr( 'name', name);    // so this button is part of the radio group

            if( trigger_needed) { $(this).trigger('click') };
        });

        // on an error redisplay the clicked radio button will be cleared
        // and the value will be in the inputbox. so, re-do the relevant click

        var val = $(where).val()
        var thisval = $(this).val()
        console.log( `${name}: ${val} thisval ${thisval}` )
        if ( $(this).val() == val ){ $(this).trigger('click') };
    });
    $( last_button).on( 'click', function(){        // the last ("other")
        $(where).show();
        $(where).attr( 'name', name );              // opposite of above
        $(last_button).attr( 'name', name+'___');
    });

    // if a radio button "owns" this value, our name will now be ___name. If
    // not and there's a value to show, don't hide  it.
    if ( !$(where).val()  || $(where).attr('name') !== name ){
        $(where).hide();                          // until last button "other" clicked
    } else {
        $( last_button).trigger('click')
    };

    $(where).before( '<span>&emsp;</span>');  //cosmetic

});
}); // end document.ready

和css文件utils/choices_with_other_widget.js(相同位置)

/* All of this except min-width is copied from .radio-inline
 * this is "external* style sheet which can be overridden
 * by an "internal" one eg Django { { block extracss } }
*/

.inline-choices-with-other {
  display: inline-block;
  position: relative;
  padding-left: 20px;
  margin-bottom: 0;
  font-weight: 400;
  vertical-align: middle;
  cursor: pointer;

  min-width: 100px;  /* this is the main one to tune for "columns" */
}

input.radio-2 {
  float: right;      /* radio buttons on right of their div (above) */
}

相关问题