class ChoiceFieldRenderer(object):
"""
An object used by RadioSelect to enable customization of radio widgets.
"""
choice_input_class = None
outer_html = '<ul{id_attr}>{content}</ul>'
inner_html = '<li>{choice_value}{sub_widgets}</li>'
def __init__(self, name, value, attrs, choices):
self.name = name
self.value = value
self.attrs = attrs
self.choices = choices
def __getitem__(self, idx):
choice = self.choices[idx] # Let the IndexError propagate
return self.choice_input_class(self.name, self.value, self.attrs.copy(), choice, idx)
def __str__(self):
return self.render()
def render(self):
"""
Outputs a <ul> for this set of choice fields.
If an id was given to the field, it is applied to the <ul> (each
item in the list will get an id of `$id_$i`).
"""
id_ = self.attrs.get('id', None)
output = []
for i, choice in enumerate(self.choices):
choice_value, choice_label = choice
if isinstance(choice_label, (tuple, list)):
attrs_plus = self.attrs.copy()
if id_:
attrs_plus['id'] += '_{0}'.format(i)
sub_ul_renderer = ChoiceFieldRenderer(name=self.name,
value=self.value,
attrs=attrs_plus,
choices=choice_label)
sub_ul_renderer.choice_input_class = self.choice_input_class
output.append(format_html(self.inner_html, choice_value=choice_value,
sub_widgets=sub_ul_renderer.render()))
else:
w = self.choice_input_class(self.name, self.value,
self.attrs.copy(), choice, i)
output.append(format_html(self.inner_html,
choice_value=force_text(w), sub_widgets=''))
return format_html(self.outer_html,
id_attr=format_html(' id="{0}"', id_) if id_ else '',
content=mark_safe('\n'.join(output)))
ChoiceFieldRenderer输出的html的格式为:
<ul>
<li><label for=" "> <input /> label_text </li>
...
</ul>
这里面所有的标签都共用 name,value,attrs中的属性。
render()函数其实含有递归调用的概念。
RadioFieldRenderer和CheckboxFieldRenderer只是指定了实例化<label ><input/>..</label>的类。
class RadioFieldRenderer(ChoiceFieldRenderer):
choice_input_class = RadioChoiceInput
class CheckboxFieldRenderer(ChoiceFieldRenderer):
choice_input_class = CheckboxChoiceInput
举个简单的例子, 以RadioFieldRenderer为例:
choices = (('apple_value', 'apple'),('banana_value', 'banana'))
radio = RadioFieldRenderer('fruit', 'banana_value', {'id': 'radio_id'}, choices)
print radio.render()
#结果为
#<ul id="radio_id">
#<li><label for="radio_id_0"><input id="radio_id_0" name="fruit" type="radio" value="apple_value" />apple</label></li>
#<li><label for="radio_id_1"><input checked="checked" id="radio_id_1" name="fruit" type="radio" value="banana_value" /> banana</label></li>
#</ul>
通过设置choices的格式,可以<li>里面嵌套<ui>。
choices = (
('Asia', (
('apple_china_value', 'apple_china'),
('apple_japan_value', 'japan_china')
)
),
('Europe', 'Europe_value')
)
radio = RadioFieldRenderer('fruit', 'banana_choosen', {'id': 'radio_id'}, choices)
print radio.render()
#结果为:
#<ul id="radio_id">
#<li>Asia<ul id="radio_id_0">
#<li><label for="radio_id_0_0"><input id="radio_id_0_0" name="fruit" type="radio" value="apple_china_value" /> apple_china</label></li>
#<li><label for="radio_id_0_1"><input id="radio_id_0_1" name="fruit" type="radio" value="apple_japan_value" /> japan_china</label></li>
#</ul></li>
#<li><label for="radio_id_1"><input id="radio_id_1" name="fruit" type="radio" value="Europe" /> Europe_value</label></li>
#</ul>
下面是RendererMixin类,它是RadioSelect,CheckboxSelectMultiple的基类。
class RendererMixin(object):
renderer = None # subclasses must define this
_empty_value = None
def __init__(self, *args, **kwargs):
# Override the default renderer if we were passed one.
renderer = kwargs.pop('renderer', None)
if renderer:
self.renderer = renderer
super(RendererMixin, self).__init__(*args, **kwargs)
def subwidgets(self, name, value, attrs=None, choices=()):
for widget in self.get_renderer(name, value, attrs, choices):
yield widget
def get_renderer(self, name, value, attrs=None, choices=()):
"""Returns an instance of the renderer."""
if value is None:
value = self._empty_value
final_attrs = self.build_attrs(attrs)
choices = list(chain(self.choices, choices))
return self.renderer(name, value, final_attrs, choices)
def render(self, name, value, attrs=None, choices=()):
return self.get_renderer(name, value, attrs, choices).render()
def id_for_label(self, id_):
# Widgets using this RendererMixin are made of a collection of
# subwidgets, each with their own <label>, and distinct ID.
# The IDs are made distinct by y "_X" suffix, where X is the zero-based
# index of the choice field. Thus, the label for the main widget should
# reference the first subwidget, hence the "_0" suffix.
if id_:
id_ += '_0'
return id_
整个思路是指定类属性 renderer的值,来确定使用那个render来输出html语句。
类属性_empty_value,仅仅用来当value=None时,默认为空时的值。
通过下面两个类,就很清楚。
class RadioSelect(RendererMixin, Select):
renderer = RadioFieldRenderer
_empty_value = ''
class CheckboxSelectMultiple(RendererMixin, SelectMultiple):
renderer = CheckboxFieldRenderer
_empty_value = []