djangoのModelFormはどこでchoicesに「-------」を入れているのか追いかけてみた

djangoのModelFormはどこでchoicesに未選択を表す「-------」を入れているのか、ちょっと気になったのでソース追っかけてました。
メモを残しておきます。

適当に作ったサンプル

models.py

from django.db import models


FAVORITE_COLORS_CHOICES = (('blue', 'Blue'),
                           ('green', 'Green'),
                           ('black', 'Black'))

class Simple(models.Model):
    favorite_color = models.CharField(choices=FAVORITE_COLORS_CHOICES, max_length=100)


forms.py

from django.forms.models import modelform_factory


SimpleForm = modelform_factory(Simple)


このSimpleFormは「--------」を勝手に追加しますが

>>> SimpleForm().as_p()
u'<p>
     <label for="id_favorite_color">Favorite color:</label>
     <select id="id_favorite_color" name="favorite_color">
       <option value="" selected="selected">---------</option>
       <option value="blue">Blue</option>\n<option value="green">Green</option>
       <option value="black">Black</option>
     </select>
   </p>'


model側でフィールドにdefault=Noneを設定してやると追加しません。

class Simple(models.Model):
    favorite_color = models.CharField(choices=FAVORITE_COLORS_CHOICES, max_length=100, default=None)
>>> SimpleForm().as_p()
u'<p>
     <label for="id_favorite_color">Favorite color:</label>
     <select id="id_favorite_color" name="favorite_color">
       <option value="blue">Blue</option>\n<option value="green">Green</option>
       <option value="black">Black</option>
     </select>
   </p>'


以降djangoの処理を追いながら作成したメモ



まず、modelform_factoryは内部でModelFormのインスタンスを作成します。

ModelFormが継承しているModelFormMetaClassの__init__が呼ばれます。

このような箇所があり、fields_for_modelがformのフィールドを設定しているようです

       if opts.model:
            # If a model is defined, extract form fields from it.
            fields = fields_for_model(opts.model, opts.fields,
                                      opts.exclude, opts.widgets, formfield_callback)


次にdjango.forms.models.fields_for_model

def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=None):
    """
    Returns a ``SortedDict`` containing form fields for the given model.

    ``fields`` is an optional list of field names. If provided, only the named
    fields will be included in the returned fields.

    ``exclude`` is an optional list of field names. If provided, the named
    fields will be excluded from the returned fields, even if they are listed
    in the ``fields`` argument.

    ``widgets`` is a dictionary of model field names mapped to a widget

    ``formfield_callback`` is a callable that takes a model field and returns
    a form field.
    """
    field_list = []
    ignored = []
    opts = model._meta
    for f in sorted(opts.fields + opts.many_to_many):
        if not f.editable:
            continue
        if fields is not None and not f.name in fields:
            continue
        if exclude and f.name in exclude:
            continue
        if widgets and f.name in widgets:
            kwargs = {'widget': widgets[f.name]}
        else:
            kwargs = {}

        if formfield_callback is None:
            formfield = f.formfield(**kwargs)
        elif not callable(formfield_callback):
            raise TypeError('formfield_callback must be a function or callable')
        else:
            formfield = formfield_callback(f, **kwargs)

        if formfield:
            field_list.append((f.name, formfield))
        else:
            ignored.append(f.name)
    field_dict = SortedDict(field_list)
    if fields:
        field_dict = SortedDict(
            [(f, field_dict.get(f)) for f in fields
                if ((not exclude) or (exclude and f not in exclude)) and (f not in ignored)]
        )
    return field_dict


formfield = f.formfield(**kwargs)でformfieldを決めているようです。
fはmodels.Fieldのサブクラス、上記のsampleの場合favoritecolorのCharFieldです。


CharFieldのformfieldはchoicesに関しては何もしません。

    def formfield(self, **kwargs):
        # Passing max_length to forms.CharField means that the value's length
        # will be validated twice. This is considered acceptable since we want
        # the value in the form field (to pass into widget for example).
        defaults = {'max_length': self.max_length}
        defaults.update(kwargs)
        return super(CharField, self).formfield(**defaults)


Fieldのformfieldをみてみると、choicesに関係ある処理がありました。

    def formfield(self, form_class=forms.CharField, **kwargs):
        """
        Returns a django.forms.Field instance for this database Field.
        """
        defaults = {'required': not self.blank,
                    'label': capfirst(self.verbose_name),
                    'help_text': self.help_text}
        if self.has_default():
            if callable(self.default):
                defaults['initial'] = self.default
                defaults['show_hidden_initial'] = True
            else:
                defaults['initial'] = self.get_default()
        if self.choices:
            # Fields with choices get special treatment.
            include_blank = (self.blank or
                             not (self.has_default() or 'initial' in kwargs))
            defaults['choices'] = self.get_choices(include_blank=include_blank)
            defaults['coerce'] = self.to_python
            if self.null:
                defaults['empty_value'] = None
            form_class = forms.TypedChoiceField
            # Many of the subclass-specific formfield arguments (min_value,
            # max_value) don't apply for choice fields, so be sure to only pass
            # the values that TypedChoiceField will understand.
            for k in list(kwargs):
                if k not in ('coerce', 'empty_value', 'choices', 'required',
                             'widget', 'label', 'initial', 'help_text',
                             'error_messages', 'show_hidden_initial'):
                    del kwargs[k]
        defaults.update(kwargs)
        return form_class(**defaults)


ここですね。
blank=Trueであるか、default、initialが無ければ、blankを入れるようです。

        if self.choices:
            # Fields with choices get special treatment.
            include_blank = (self.blank or
                             not (self.has_default() or 'initial' in kwargs))
            defaults['choices'] = self.get_choices(include_blank=include_blank)


Fieldのget_choicesをみてみると、include_blankの値を見て、blank_choiceをchoicesに追加する処理があります。

    def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH):
        """Returns choices with a default blank choices included, for use
        as SelectField choices for this field."""
        first_choice = include_blank and blank_choice or []
        if self.choices:
            return first_choice + list(self.choices)
        rel_model = self.rel.to
        if hasattr(self.rel, 'get_related_field'):
            lst = [(getattr(x, self.rel.get_related_field().attname),
                        smart_text(x))
                   for x in rel_model._default_manager.complex_filter(
                       self.rel.limit_choices_to)]
        else:
            lst = [(x._get_pk_val(), smart_text(x))
                   for x in rel_model._default_manager.complex_filter(
                       self.rel.limit_choices_to)]
        return first_choice + lst


BLANK_CHOICES_DASHはdjango.db.models.fields.__init__に定義されてます。

# The values to use for "blank" in SelectFields. Will be appended to the start
# of most "choices" lists.
BLANK_CHOICE_DASH = [("", "---------")]


一連の処理はmodelsのfieldsから、formのfieldsを作成するときの処理なので、ModelFormでない、forms.Formの場合は関係がない。
その場合、choicesにblankを追加したい場合は、自分で追加することになる。

こんな感じ
django - ChoiceField doesn't display an empty label when using a tuple - am I doing something wrong? - Stack Overflow