Overriding django's CheckboxSelectMultiple widget for Awesome Bootstrap Checkboxes

时间:2016-08-31 18:56:34

标签: django

If I print an instance of the following form in a template:

class StrategiesForm(forms.Form):
    letters = MultipleChoiceField(
        choices=((0, 'a'),(1, 'b'),(2, 'c')),
        label="a_label",
        required=True,
        widget=CheckboxSelectMultiple(),
    )

I get:

<label for="id_letters_0">a_label:</label>
<ul id="id_letters">
    <li>
        <label for="id_letters_0">
            <input id="id_letters_0" name="letters" type="checkbox" value="0"> a
        </label>
    </li>
    <li>
        <label for="id_letters_1">
            <input id="id_letters_1" name="letters" type="checkbox" value="1"> b
        </label>
    </li>
    <li>
        <label for="id_letters_2">
            <input id="id_letters_2" name="letters" type="checkbox" value="2"> c
        </label>
    </li>
</ul>

This is basically bunch of inputs embedded in labels, which are embedded in list items.

For Awesome Bootstrap Checkboxes to work I need to change the layout a bit. Specifically, inputs must be above the labels not in them. Also, both the input and the label need to wrapped in a div with class "checkbox checkbox-primary", instead of a li.

For the above example this would be:

<label for="id_letters_0">a_label:</label>
<div id="id_letters">
    <div class="checkbox checkbox-primary">
        <input id="id_letters_0" name="letters" type="checkbox" value="0"> a
        <label for="id_letters_0"></label>
    </div>
    <div class="checkbox checkbox-primary">
        <input id="id_letters_1" name="letters" type="checkbox" value="1"> b
        <label for="id_letters_1"></label>
    </div>
    <div class="checkbox checkbox-primary">
        <input id="id_letters_2" name="letters" type="checkbox" value="2"> c
        <label for="id_letters_2"></label>
    </div>
</div>

How can I accomplish this by defining a new widget, preferably overriding django's existing?

1 个答案:

答案 0 :(得分:1)

这是django在执行顺序或类层次结构中的默认代码:

class CheckboxSelectMultiple(RendererMixin, SelectMultiple):
    renderer = CheckboxFieldRenderer            # contains all layout logic
    _empty_value = []


class CheckboxFieldRenderer(ChoiceFieldRenderer): # parent has outer layout
    choice_input_class = CheckboxChoiceInput    #  contains inner layout


class ChoiceFieldRenderer(object):              # outer layout: ul and li
    outer_html = '<ul{id_attr}>{content}</ul>'
    inner_html = '<li>{choice_value}{sub_widgets}</li>'

    def render(self):       # this generates inner layout, and wraps with outer
        ...

class CheckboxChoiceInput(ChoiceInput):
    input_type = 'checkbox'
    ...


class ChoiceInput(SubWidget):                   # inner layout: label and input 
    ...
    def render(self, name=None, value=None, attrs=None, choices=()):
        if self.id_for_label:
            label_for = format_html(' for="{}"', self.id_for_label)
        else:
            label_for = ''
        attrs = dict(self.attrs, **attrs) if attrs else self.attrs
        return format_html(
            '<label{}>{} {}</label>', label_for, self.tag(attrs), self.choice_label
        )
    ...

现在我们反过来覆盖必要的部分:

class AwesomeChoiceInput(ChoiceInput):
    def render(self, name=None, value=None, attrs=None, choices=()):
        if self.id_for_label:
            label_for = format_html(' for="{}"', self.id_for_label)
        else:
            label_for = ''
        attrs = dict(self.attrs, **attrs) if attrs else self.attrs
        return format_html(
            '{}\n<label{}> {}</label>', self.tag(attrs), label_for, self.choice_label   # updated!
        )


class AwesomeChoiceFieldRenderer(ChoiceFieldRenderer):
    outer_html = '<div{id_attr}>{content}</div>'                                            # updated!!
    inner_html = '<div class="checkbox checkbox-primary">{choice_value}{sub_widgets}</div>' # updated!!
    def __init__(self, name, value, attrs, choices):
        super().__init__(name, value, attrs, choices)
        if 'awesome-class' in attrs:
            self.inner_html = '<div class="checkbox ' + attrs.pop('awesome-class') + '">{choice_value}{sub_widgets}</div>'

class AwesomeCheckboxChoiceInput(AwesomeChoiceInput, CheckboxChoiceInput): # this was little tricky
    # there might be better ways of extending a class just to override its parent
    # summary: class A(B) => class extendedA(extendedB, A) # to get whatever was in A also
    pass


class AwesomeCheckboxFieldRenderer(AwesomeChoiceFieldRenderer):  # rest-just connect the pipes
    choice_input_class = AwesomeCheckboxChoiceInput              # as you took off :)                             


class AwesomeCheckboxSelectMultiple(CheckboxSelectMultiple):
    renderer = AwesomeCheckboxFieldRendereraa