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?
答案 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