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を追加したい場合は、自分で追加することになる。