django.forms.widget-ChoiceFieldRenderer

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


你可能感兴趣的:(django.forms.widget-ChoiceFieldRenderer)