BLOGサブスレッドの日常

2016.05.31

DjangoのFormを活用する (1) ModelChoiceField

mzsm

株式会社サブスレッドのみずしま a.k.a mzsm(@mzsm_j)です。こんにちは。

DjangoのForm、ちゃんと使えばかなり優秀でいろいろと楽できるですけど、できることが多すぎて使いこなせてないケースもあるかと思います。
今回はその中でもModelChoiceFieldについて。

「DBに格納されている値の中からどれか選ばせたい?そんなときModelChoiceFieldが便利なんですよ。」
…ということはDjango使いなら皆知っているかと思いますが、ちゃんと使いこなせてますか…?

例えばこのようなモデルとテーブルが存在しているとします。

from django.db import models

class Product(models.Model):
    """商品モデル"""
    code = models.CharField('商品管理番号', max_length=30, unique=True)
    name = models.CharField('商品名', max_length=100)

    def __str__(self):
        return self.name
id code name
1 MIKAN みかん
2 RINGO りんご
3 BANANA バナナ

そして、このようなフォームを作成すると…

from django import forms

from . import models

class ProductForm(forms.Form):
    product = forms.ModelChoiceField(models.Product.objects, label='商品')
    amount = forms.IntegerField(min_value=1, max_value=10, label='個数')

だいたいこんな感じのHTMLが出力されます(実際にはidやら何やらが入りますがここでは省略)

<form>
    <div>
        <label>商品</label>
        <select>
            <option value="">---------</option>
            <option value="1">みかん</option>
            <option value="2">りんご</option>
            <option value="3">バナナ</option>
        </select>
    </div>
    <div>
        <label>個数</label>
        <input type="number">
    </div>
</form>

それではこれをもとにいろいろいじっていきましょう。

未選択の場合の選択肢を変える

項目が未選択の場合、デフォルトでは---------という項目が表示されますが、これを例えば選択してくださいに変えたい場合はempty_labelを指定します。

from django import forms

from . import models

class ProductForm(forms.Form):
    product = forms.ModelChoiceField(models.Product.objects, label='商品',
                                     empty_label='選択してください')
    amount = forms.IntegerField(min_value=1, max_value=10, label='個数')

↓出力HTML

<form>
    <div>
        <label>商品</label>
        <select>
            <option value="">選択してください</option>
            <option value="1">みかん</option>
            <option value="2">りんご</option>
            <option value="3">バナナ</option>
        </select>
    </div>
    <div>
        <label>個数</label>
        <input type="number">
    </div>
</form>

valueをid以外にする

valueはデフォルトでは主キー(pk=id)が使用されますが、シーケンシャルなIDをユーザーにあまり見せたくないとか、業務で利用しているコードを使用したいといったような場合には、to_field_nameにフィールド名を指定することで、そのフィールドを値として指定できます。

from django import forms

from . import models

class ProductForm(forms.Form):
    product = forms.ModelChoiceField(models.Product.objects, label='商品',
                                     empty_label='選択してください', to_field_name='code')
    amount = forms.IntegerField(min_value=1, max_value=10, label='個数')

↓出力HTML

<form>
    <div>
        <label>商品</label>
        <select>
            <option value="">選択してください</option>
            <option value="MIKAN">みかん</option>
            <option value="RINGO">りんご</option>
            <option value="BANANA">バナナ</option>
        </select>
    </div>
    <div>
        <label>個数</label>
        <input type="number">
    </div>
</form>

セレクトボックスからの選択ではなく、値を直接指定させる

候補一覧を表示して選択させるのではなく、ユニークなコードを直接入力させるほうが便利な場合もあるでしょう。
widgetTextInputに変更することで、セレクトボックスではなく文字列で入力させられるようになります。
これにより、ModelChoiceFieldを簡単な検索フォームの部品として利用できます。
CharFieldを使う場合、is_valid()でフォームの妥当性を検証した上で、改めてモデルを検索してそのコードに一致するレコードがあるかどうかを調べる、という2段階で処理を行う必要がありますが、
ModelChoiceFieldを使えばis_valid()を呼び出すだけでそのコードに一致するレコードがあるか調べられるので、ちょっぴり楽です。

from django import forms

from . import models

class ProductForm(forms.Form):
    product = forms.ModelChoiceField(models.Product.objects, label='商品',
                                     empty_label='選択してください', to_field_name='code',
                                     widget=forms.TextInput)
    amount = forms.IntegerField(min_value=1, max_value=10, label='個数')

↓出力HTML

<form>
    <div>
        <label>商品</label>
        <input type="text">
    </div>
    <div>
        <label>個数</label>
        <input type="number">
    </div>
</form>

このとき、存在しないコードを入力すると、正しく選択してください。選択したものは候補にありません。というメッセージが表示されます。
選択しているわけではないので、このエラーメッセージだと違和感があります。
error_messagesを指定して、違和感のないエラーメッセージを表示するようにするのがよいでしょう。

from django import forms

from . import models

class ProductForm(forms.Form):
    product = forms.ModelChoiceField(models.Product.objects, label='商品',
                                     empty_label='選択してください', to_field_name='code',
                                     widget=forms.TextInput,
                                     error_messages={'invalid_choice':'このコードの商品は存在しません'})
    amount = forms.IntegerField(min_value=1, max_value=10, label='個数')

こちらからは以上です。

この記事を書いた人

mzsm