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 = []