This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 3 years have passed since last update.

[Day 17]FromとHTMLのレンダリングの関係を理解する

Last updated at Posted at 2021-01-22

January 22, 2021
←前回:Day 16 FormとModelForm

「Djangoを学びたい」とのことでありましたら[Day 1]Djangoの開発環境から読むことをおすすめします。

はじめに

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

これまでのおさらい

現時点までで、テンプレートに渡されたフォームは{{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化しています。

templates/thread/create_topi.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 %}

ブラウザで確認すると以下のように見える筈です。
image.png
エラーメッセージはインプットタグの下に赤字で表示されるように設定しています。errorsは複数ですので注意して下さい。

終わりに

いつもは指が冷たく、タイピングのスピードがなかなか出せませんでしたが、今日は比較的に指がいうことを聞いてくれました。

それではまたまた

←前回:Day 16 FormとModelForm
→次回:Day 18 FormViewとCreateViewを使う

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up