1-16. FormとHTMLレンダリングの関係を理解する

今回のテーマは「FormとHTMLレンダリングの関係を理解する」です。Django使い始めてフォームの扱いがややこしいという話はよく聞きます。その1つにフォームが担う役割が多くて混乱するというのがあると思います。今回はテンプレートに渡されたフォームをどのように扱えば自分の望むHTMLが得られるのかを中心に見ていきます。

※本ページはFormとModelFormまで読まれた方を対象としています。そのためサンプルソースコードが省略されている場合があります。


ここまでのおさらい

現時点までで、テンプレートに渡されたフォームは{{form}}や{{form.as_p}}、{{form.as_table}}等で表示できるという説明をしてきました。そうすることで入力させたい項目全ての入力フォームをまとめてレンダリングしてくれる便利機能として扱ってきました。しかし、見た目の細かい調整をさせようと思うと突然融通がきかない不便さを感じます。つまり{{for.as_p}}は常に

<p><label for="id_title">タイトル:</label> <input type="text" name="title" maxlength="2" required id="id_title"></p>
<p><label for="id_user_name">お名前:</label> <input type="text" name="user_name" maxlength="30" required id="id_user_name"></p>
<p><label for="id_category">カテゴリー:</label> <select name="category" required id="id_category">
  <option value="" selected>選択して下さい</option>

  <option value="1">WEB技術</option>
  <option value="2">モバイル</option>
  <option value="3">プログラミング</option>
  <option value="4">OS関連・インフラ</option>
  <option value="5">業界ネタ・憩いの場</option>

</select></p>
<p><label for="id_message">本文:</label> <textarea name="message" cols="40" rows="10" required id="id_message">
</textarea></p>

のように表示されます。例えば「タイトル」と「お名前」の間に何か挿入したいと思っても出来ません。順序を入れ替えたい。CSSのクラスを挿入したい。もっとフレキシブルにフォームをレンダリングする方法はないのでしょうか?

フォームを使わないで全てHTMLを手書きする

まず極論しますとテンプレートに渡されたフォームを使わなくても全てHTMLで手書きすればOKです。name属性の名前さえ間違えなければ無事POSTすることは出来ます。バリデーション処理を行う関数を別に書いて、結果をテンプレートに渡してエラー表示させるということもフォームを使わずとも出来ます。とにかくサイトを早く完成させたい。フォームなどに付き合ってられない。という場合はこれでもサイトは出来ます。ただし、非常にもったいないです。

入力項目ごとにフォームをレンダリングする

実はフォームはバラバラにレンダリングすることが可能です。views.py等のプログラム上ではform['{フィールド名}']で、テンプレート内ではform.{フィールド名}で各フィールドにアクセスできます。各フィールドには以下の属性があります。

  • label:ラベル用のテキスト
  • label_tag:ラベルのHTMLタグ
  • id_for_label:ラベルタグ用のid
  • value:インプットタグ用のvalue要素の値
  • help_text:説明文言
  • html_name:インプットタグのname要素の値
  • errors:エラーメッセージの集まり
  • field:フォームクラスのプロパティ

errorsはErrorDict型の集合体なのでfor文を使うなど注意が必要です。fieldに至っては謎ですよね。公式ドキュメントを読んだ時も{{ field.field }}と書いてあり唖然としました。実はこれはフォームクラスの各フィールドにアクセスしているのです。つまり前回扱ったTopicFormで説明すると各プロパティであるtitleやmessageにアクセス出来ます。これを利用してviews.pyでもwidgetやmax_lengthにアクセス出来るようになるというわけです。

話を戻して、ここでは入力項目毎にレンダリングできるということを説明します。例えばタイトルの入力フォームだけをレンダリングする場合は以下の様にできます。

<p>
  {{form.title.label_tag}}
  {{form.title}}
  {% for error in form.title.errors %}
      {{error}}
  {% endfor %}
</p>

ここでテンプレート中にforループが出てきましたね。上記のようにpythonの文法と非常によく似た方法でforループを書くことが出来ます。endforを忘れないように注意して下さい。

フォームごとforループでレンダリングすることも可能です。よって{{form.as_p}}は以下のように書くことが出来ます。

{% for field in form %}
<p>
  {{field.label_tag}}
  {{field}}
  {% for error in field.errors %}
      {{error}}
  {% endfor %}
</p>
{% endfor %}

今回の掲示板の例ではforループを使って以下のようにレンダリングすることにします。今回はSemanticのクラスも追加しながらHTML化しています。create_topic.htmlファイルを以下のように修正しましょう。

templates/thread/create_topic.html

{% extends 'base/base.html' %}
{% block title %}トピック作成 - {{ block.super }}{% endblock %}
{% block content %}
<div class="ui grid stackable">
  <div class="eleven wide column">
    <div class="ui breadcrumb">
      <a href="{% url 'base:top' %}" class="section">TOP</a>
      <i class="right angle icon divider"></i>
      <a class="active section">トピック作成</a>
    </div>
    <div class="ui segment">
      <div class="content">
        <div class="header"><h3>トピック作成</h3></div>
        <form class="ui form" action="{% url 'thread:create_topic' %}" method="POST">
          {% csrf_token %}
          {% for field in form %}
          <div class="field">{{field.label_tag}}{{field}}</div>
            {% for error in field.errors%}
            <p style="color: red;">{{error}}</p>
            {% endfor%}
          {% endfor %}
          <button type="submit" class="ui button">作成</button>
        </form>
      </div>
    </div>
  </div>
  {% include 'base/sidebar.html' %}
</div>
{% endblock %}

ブラウザで確認すると以下のように見える筈です。

エラーメッセージはインプットタグの下に赤字で表示されるように設定しています。errorsは複数ですので注意して下さい。

最後に

ここまでフォームの使い方を中心にユーザーが入力した情報をDBに保存する方法を見てきました。次回はクラスベースビューを使って少ないコード量でデータの登録を実装する方法を見ていきましょう。

Sponsored Link


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です