この記事はDjango Advent Calendar 2019 3日目の記事です。
(アドベントカレンダーは)初投稿です。
はじめに
ここ1ヶ月くらいDjangoで個人的にアプリを作ってる非エンジニアの人です。
そのアプリで外部キーを使用したところ、プルダウンを開くと100件以上表示されてしまうので、
なんとか絞れないかなと思いました。
先に結論を言えば、limit_choices_to
を使うことで、外部キーで表示されるレコードをfilterすることが可能です。
どのように使えば良いか、実際にアプリを作成して試してみます。
この記事の為に作ったアプリ
説明の為に簡単なアプリを作成しました。Modelだけです。
ちなみにテーマとしては、2019年にFA権を取得した選手達がどの球団に行くかの予想を登録するアプリです。
もうだいたい決まったから実用性はないな!
https://github.com/shimayu22/fa_expects_app/
FaExpectsテーブルでは、Playersテーブルを外部キー設定し、登録されている選手達がプルダウンで選択できるようにしています。
選手データも用意しているので読み込むだけでお試しできます。
使い方はREADMEを参照してください。
こういうこと
※python manage.py runserver
で検証しています
http://127.0.0.1:8000/admin/fa_expects/faexpects/add/
の「選手」のプルダウンを開くと、
画像のようにズラーっと表示されてしまいます。
管理画面でFilterできんのか、という話です。
limit_choices_toを利用する
DjangoドキュメントのForeignKey.limit_choices_toより、
このフィールドがModelFormまたはadmin を使用してレンダリングされるときに、このフィールドで使用可能な選択肢に制限を設定します(デフォルトでは、クエリセット内のすべてのオブジェクトを選択できます)。
辞書、Qオブジェクト、または辞書またはQオブジェクトを返す呼び出し可能オブジェクトのいずれかを使用できます。
(Google翻訳)
ということなので、limit_choices_to
を使えば選択できる項目をFilterすることができます。
実際にやってみた
FaExpectsのplayer_idにlimit_choices_toを追加しました。
player_id = models.ForeignKey(
Players,
on_delete=models.CASCADE,
verbose_name="選手",
limit_choices_to={"position":1,}
)
今回はpositionが「1」(投手)だけ選択できるようにします。
これで、
http://127.0.0.1:8000/admin/fa_expects/faexpects/add/
の「選手」のプルダウンを開くと、
見事、投手として登録した選手だけ表示されました!
検索キーワードを使用する
上記の例だと、position == 1
など、イコールでしか絞れませんが、検索キーワードを使用することで、以上、以下などを用いて絞ることが可能です。
player_id = models.ForeignKey(
Players,
on_delete=models.CASCADE,
verbose_name="選手",
limit_choices_to={"position": 1,
"age__lt": 33}
)
上記はposition == 1 and age < 33
(投手かつ33歳未満)という条件を付けます。
項目名age
に__lt
(アンダーバー2つ)をつけてると「~未満」という条件が加わります。
選択できる選手が少なくなりましたね!
その他のキーワードについては下記の記事がとてもよくまとまっています。
参考:Django データベース操作 についてのまとめ#検索キーワードの一覧
Qオブジェクトを利用する
これまでは辞書型で検索条件を指定していましたが、Qオブジェクトでも指定することが可能です。
from django.db.models import Q
~~~省略~~~
player_id = models.ForeignKey(
Players,
on_delete=models.CASCADE,
verbose_name="選手",
limit_choices_to=Q(position=4) | Q(position=7),
)
Qオブジェクトだと、このようにOR条件を指定できます。
上記では「ポジションが二塁手 or 外野手」という意味になります。
見事、二塁手または外野手だけ表示することができました!
limit_choices_to用の辞書を作る
べた書きだと柔軟性がないので、関数で辞書型を作って渡してあげます。
今回はサクッと作るために、条件を指定する用のテーブルを作りました。
Models.py
にRequestedConditions
クラスを追加しています。
(長いのでModels.pyで確認してください)
そして、RequestedConditions
テーブルの最新レコードからlimit_choices_to用の辞書を作成するための関数を作成しました。
〜〜〜省略〜〜〜
def set_players_condition():
condition = RequestedConditions.objects.latest('pk')
condition_dict = {}
if condition.age > 0:
condition_dict["age__lt"] = condition.age
if condition.position > 0:
condition_dict["position"] = condition.position
if condition.dominant_hand > 0:
condition_dict["dominant_hand"] = condition.dominant_hand
return condition_dict
〜〜〜省略〜〜〜
まず、
http://127.0.0.1:8000/admin/fa_expects/requestedconditions/add/
より、「年齢」「ポジション」「利き手」を設定し、保存します。
(全部設定しなければ全部表示されます)
登録後、
http://127.0.0.1:8000/admin/fa_expects/faexpects/add/
の選手を見てみると、条件にあった選手のみ表示されます。
やったぜ。
おわりに
モデルだけでちょろっとやりたい時はこんな感じでできます。
Qオブジェクトも組み合わせればもっと柔軟にできそうですね。
色々いじって試してみてください。
おまけ
冷静に考えれば当たり前かもしれませんが、
limit_choices_to={"age__lt":RequestedConditions.objects.latest('pk').age}
みたいな感じで書いてしまうと、最初にmigrateした時に「そんなテーブルないぞ」というエラーが出ます。
そら(まだ作成されていないテーブルを参照しようとしたら)そう(エラーが出る)よ(当たり前じゃないか)。
あまり関係ない補足
- FA宣言した選手達ではなく、FA権を取得した選手達です(間違いがあったらゴメンね)
- 鈴木大地(ロ)は色々守れすぎなので、2019年一番多く守ってるっぽい(一)としました
- 私はちなヤクちなハムです
- 来シーズンもやきう楽しみですね!
参考
また、Django全般で下記記事が参考になりました。ありがとうございます!
[Python] Djangoチュートリアル - 汎用業務Webアプリを最速で作る
[Django] モデルフィールド 設定テンプレート