nikkie-ftnextの日記

イベントレポートや読書メモを発信

自作Djangoミニアプリ、テンプレートで頑張っていた検索フォームの実装をDjangoフォームで書き直しました

この記事は、Django Advent Calendar 2022 8日目の記事です。
昨日は、おっと! 枠がまだ空いているようですね。
Djangoに興味があるそこのあなた! よろしければ時を戻した投稿をしてみませんか?🎸

はじめに

楽してこーぜ!、百沢1じゃなくてnikkieです。

Django Advent Calendarに一ネタ投稿です!
私はDjangoを実務でバリバリ使っているわけではありません。
趣味開発のような感じで、PyCon JPのスタッフ活動でごく小規模なアプリを開発し、他のスタッフにも使っていただきました。
その中で学んだDjangoフォームについて共有していきます。

目次

背景:PyCon JPスタッフ活動で作ったDjangoアプリ

PyCon JPスタッフ活動の中で、プロポーザル(「私はこういうトークをしたい」という文書)のレビューに使うアプリをDjangoで自作しました2
2020年、2021年(座長の年!3)と2回作っています。

2回目(2021年)は、1回目(2020年)でイマイチだった点のリベンジが個人的なテーマでした4
そのうちの1つが、レビュアーがプロポーザルを検索できるフォームです5

レビュアーがプロポーザルを検索できるフォーム

プロポーザルレビューアプリでは、2020年のレビュー中の声を元に、以下の項目で検索できる機能を追加しました(2021年でも継続して実装)。

  • 時間枠(2020年のみ)
  • 聴衆のPythonのレベル(Beginner/Intermediate/Advanced)(2021年のみ)
  • 発表の言語
  • セッションのカテゴリ6
  • セッションタイトルに含まれる語(あいまい検索)
  • 自身がすでにレビューしたプロポーザルを除いて検索

このフォームはプロポーザルの一覧画面にあります。
モデルには結びつかないフォームです(ModelFormは考えませんでした)。
検索して遷移した一覧画面ではフォームの入力値(検索条件)を保持して表示するようにもしています(テンプレートタグを作って対応)。

2020年(Django 3.0系で開発)はテンプレートで実装しました。
ビューからテンプレートにデータを渡し、テンプレートタグをゴリゴリ使って検索条件を保持しつつフォームを描画します。

実装を終えて振り返ると、「テンプレートをゴリゴリ書くのが正解だったのだろうか? もっとうまくやる方法があったのでは?」と疑問に思えてきました。
そこで2021年の機会(Django 3.2系で開発)ではDjangoフォームを試しています。
Djangoフォームで実装する中では、Pythonをゴリゴリ書きました。
2020年よりもPython(慣れた言語)に触る時間が長く、その中で発見も多くあり、個人的には開発していて楽しかったです。

テンプレートによる実装(2020年)

テンプレートは以下のようになっています(一部に絞っています)。

ビューから変数を渡します。

@login_required
def list_proposals(request):
    # 省略

    # selectタグの表示とvalueを別にするために辞書を作っています(共通の場合はcontextにあるように.valuesだけ渡します)
    speaking_language_map = {
        label: value for value, label in Proposal.SpeakingLanguage.choices
    }

    context = {
        "page_obj": page_obj,
        "proposals_count": paginator.count,
        # 以下はプロポーザル検索フォームの表示に使う
        "session_formats": Proposal.SessionFormat.values,
        "speaking_languages": speaking_language_map,
        "categories": Proposal.SessionCategory.values,
    }
    return render(request, "review/list_proposals.html", context)

ProposalモデルのTextChoices7を使って、フォームの<select>タグに必要な情報(=すべての選択肢)を送り込みます。

表示上、検索条件を保持するために、組み込みタグのwithを使いました。

複雑な表現の変数の値をキャッシュし、簡単な名前で参照できるようにします。

ユーザが選んでいる選択肢をテンプレートで知りたかったのです。

実装詳細です。
request.GETには検索条件を表すクエリパラメタがあります。
自作のテンプレートタグparse_search_parameterでクエリパラメタから値を取得します。
そして、クエリパラメタに含まれる値(=ユーザが選んでいる値)をwithタグを使って保持します。
フォームの描画ではwithタグを使って、保持した値と一致する<select>タグにだけselectedが付くようにします。
以上でユーザが選んでいる選択肢がフォームに表示されます。

Djangoフォームによる実装(2021年)

2021年の実装は以下のリポジトリで公開しています(先ほどの画像も2021年版です)。

forms.pyにプロポーザル検索用のフォームを定義しました。

テンプレートでDjangoのタグ(組み込み・自作両方)とHTMLを駆使した経験からは、かなり楽になりました。

ビューでの扱いはこんな感じです8

@login_required
def list_proposals(request):
    # 省略

    form = ProposalSearchForm(request.GET)
    context = {
        "page_obj": page_obj,
        "proposals_count": paginator.count,
        # 以下はプロポーザル検索フォームの表示に使う
        "form": form,
    }
    return render(request, "review/list_proposals.html", context)

選択肢を表示するために送るデータがなくなり、かなりスッキリしました✨

フォームを構成するデータに注目すると違いは歴然です。
Djangoフォームを使う実装ではフォームを定義し、テンプレートではrequest.GETを渡してインスタンス化するだけです。
こんなに単純な実装になるなんて!

Djangoフォームを使う実装で対処しきれなかったのは、チェックマーク✅の表示です。
非表示にしたいと思ったのですが、2021年当時は調べ切るには時間が足りず、「影響は表示レベルなので実装者のこだわりの問題」と対応優先度を下げ、そのままとしました。
宿題としておきます〜

学び:Djangoフォームを使おう!

モデルに結びつかないフォームにもDjangoフォームを使うのがオススメです。
たしかにテンプレートで頑張れるかもしれません。
ただ、ユーザがフォームに入力したデータを表示上保持する実装は、テンプレートで頑張るよりDjangoフォームを使ったほうが簡潔になります。

2020年のnikkie氏の言ですが、DjangoではModelFormしか知らず9Formはなんかよく分からないから、テンプレートでやっちゃった」とのことです。
やっちゃった後に違和感を覚えてDjangoフォームに対峙したわけですが、回り道をした今なら言えます、「Djangoフォームを使おう!」

終わりに

このアウトプットが2020年の私と同じく「モデルに結びつかない検索フォーム、知らないFormを使うか、知っている知識でできそうなテンプレートでやっちゃうか」迷っている方の意思決定の一助となったらいいなと思います(時を戻そう!)。
オススメはDjangoフォームです。
コード量が段違いで少ないですし、Pythonを書くだけで実現できます!
楽してこーぜ🎸

明日のDjango Advent Calendar 2022 9日目は、この記事を読んでくださった あなた かもしれません。
私は「アドベントカレンダーだからと気張らずに淡々と自分のペースで小ネタをアウトプット」という立場です。
もしよければ小ネタだったり、「自分の知ってるこれ、実は小ネタじゃないのかも」だったりを共有していただけるととても嬉しいです(絶対読みますからね!)

P.S. Djangoフォーム周りの積ん読

より深く理解するのに役立ちそうな積ん読(崩し中)リストを共有します。

『実践Django

第5章がDjangoフォームです。
ModelFormが詳しいです。
『実践Django』はDBやセキュリティ(例:CSRF)など裏の仕組みが詳しく分かるのがありがたいですね。

『現場で使えるDjangoの教科書』

第8章がフォームです。
FormModelFormも紹介されていますね。

Django Congress JP 2021 「理解して使いこなすDjangoのForm機能」

きゅうたつさんによるフォームのトーク、非常に丁寧でした!

復習するとよさそうですね(https://djangocongress.jp/2021.html からアーカイブも見られます)

Django Congress JP 2022 「Djangoで管理する全文検索エンジン

あいまい検索で思い出した「全文検索は素振りしたいな〜」という気持ちのままに積んでおきます。

Elasticsearch触ってみたい〜


  1. ハイキュー!!より。アニメでは4期(TO THE TOP)。稲荷崎戦めっちゃ熱かった🏐 全部見てから[禁則事項]に打ち震えました
  2. このアプリはHerokuにデプロイしていましたが、無料プラン終了に伴い、先日Web APIを生やしてデータをエクスポートし、お片付けしました。 プロポーザルやレビューがどんなデータかも簡単に書いていますので、よろしければ合わせてご参照ください
  3. ちょっとしたWebアプリなら自作できる座長です😘。今後は自動化やアプリケーション開発などコードでコミュニティにコントリビュートしていきたいな〜(お話ウェルカム)
  4. 私のアツいスタッフ活動!技術で(も)支えたPyCon JP 2021 #pyconjp - nikkie-ftnextの日記 にて、「プロポーザルレビューアプリ」の技術的なアップデートを共有しています(ここにフォームはありませんね)
  5. テストデータを入れたバージョンを静的化しています。画像も静的化したバージョンです(「絞り込む」ボタンを無効化)
  6. Trackとも呼ぶかもしれません
  7. https://docs.djangoproject.com/ja/3.2/ref/models/fields/#enumeration-types
  8. https://github.com/pyconjp/pycon.jp.2021.review/blob/352ec3f2ac5d9473ed723882597287f23f4e79ab/reviewsite/review/views.py#L24-L50
  9. Django Girls Tutorial育ちなので、ModelFormは手に馴染みまくっていました。 ref: Djangoフォーム · HonKit