【Django】FormViewの使い方(クラスベースビューで汎用的なフォームを扱う)

FormViewでのクラスベースビューの実現方法解説ページアイキャッチ

このページにはプロモーションが含まれています

このページでは、Django フレームワークで定義される FormView の使い方について説明していきます。

この FormViewView というクラスのサブクラスの1つであり、ビューをクラスベースで作成する際に利用するクラスとなります。この View のサブクラス を継承し、さらにクラス変数を定義したりメソッドをオーバーライドすることで、あなたが開発したいアプリに応じたビューを作成することが可能となります。

この辺りのクラスベースビューやクラスベースビューの作り方については下記ページで解説していますので、詳しくはこちらをご参照ください。

クラスベースビューの解説ページアイキャッチ 【Django入門15】クラスベースビューの基本

今回は、多数存在する View のサブクラス の中から FormView に焦点を当てて解説を行なっていきます。

FormView

では、FormView について解説していきたいと思います。

FormView は、フォームを扱うページを実現する View のサブクラス となります。

FormViewdjango.views.generic から import して利用します。

FormViewのimport
from django.views.generic import FormView

FormView は基本的にデータベース操作なし

実は、Django フレームワークにはフォームを扱うページを実現する View のサブクラス が他にも用意されています。代表的なフォームを扱う View のサブクラス としては、CreateViewUpdateView が挙げられます。これらに関しては下記のページで詳細を解説していますので、詳しくは下記ページを参照していただければと思います。

CreateViewでのクラスベースビューの実現方法解説ページアイキャッチ 【Django】CreateViewの使い方(クラスベースビューでの新規登録ページの実現) UpdateViewでのクラスベースビューの実現方法解説ページアイキャッチ 【Django】UpdateViewの使い方(クラスベースビューでのレコード更新ページの実現)

これらの CreateViewUpdateView と、FormView との決定的な違いは、データベース操作を行うことを前提として定義されているかどうかという点にあります。

CreateView はフォームを扱い、ユーザーがフォームから送信してきたデータからインスタンスを生成し、それをレコードとしてデータベースの特定のテーブルに新規登録することを前提とした View のサブクラス となります。また、UpdateView は、既にデータベースの特定のテーブルに存在しているレコードをユーザーがフォームから送信してきたデータから生成したインスタンス(レコード)で上書き・更新することを前提とした View のサブクラス となります。

このように、これらの View のサブクラス ではフォームから送信されてきたデータに基づいてデータベースの操作を行うことを前提としたクラスとなっています。そもそも、フォームとデータベースのレコードは密接に関連しており、ウェブアプリではデータベースの操作をユーザーからの入力に基づいて実施することを目的にフォームを扱うことが多いです。例えば、ユーザー登録を行うウェブアプリでは、ユーザ名やメールアドレス等を入力するフィールドを備えたフォームを表示し、そのフィールドに入力されたデータから生成したインスタンスをレコードとしてユーザー管理用のテーブルに保存が行われることになります。

CreateViewが送信されてきたデータに基づいてデータベース操作を行う様子

また、Django において、データベースのテーブルはモデルクラスによって表現され、さらにテーブルのレコードは、そのモデルクラスのインスタンスとして扱われます。さらに、そもそもフォームはデータベースの特定のテーブルに対して操作を行うことを目的として利用する機会が多いため、Django にはフォームをモデルクラスに基づいて自動的に生成するような仕組みも存在しています。それが、下記ページでも紹介しているモデルフォームクラスになります。そして、前述で紹介した CreateViewUpdateView は、フォームとしてモデルフォームクラスを扱うことを前提としたものとなっています。

Djangoのモデルフォーム解説ページアイキャッチ 【Django入門8】モデルフォームの基本

こういった前提に基づいて定義されている View のサブクラス であるため、CreateViewUpdateView を利用することで、データベースの特定のテーブルに対してレコードの新規登録やレコードの更新を行うようなビューを簡単に実現することが可能となります。

しかし、機会が多いだけで、フォームを扱うからといって必ずしもデータベースの操作が行われるとは限りません。例えば、2つの数字の入力を受け付けるフォームを表示し、ユーザーから入力された2つの数字の掛け算結果を表示するようなビューの場合、わざわざ入力された数字をデータベースに保存する必要はなく、単にフォームから送信されてきた2つの数字の掛け算結果をページに表示してやれば良いだけになります。

データベース操作を行わずにレスポンスを返却する様子

このように、フォームは扱うもののデータベースの操作を伴わないビューを実現する場合、データベースの操作を行うことを前提として定義されている CreateViewUpdateView を利用するのでは、むしろ実現が難しくなります。

なので、データベースの操作を行うことを前提としていない View のサブクラス を利用した方が良いです。そして、そのデータベースの操作を行うことを前提としていないフォームを扱うView のサブクラス が、このページで説明する FormView となります。

FormViewが送信されてきたデータに基づいたデータベース操作を行わ図にレスポンスを返却する様子

このように、FormView はフォームは扱うもののデータベース操作を伴わないビューを実現するのに適した View のサブクラス となります。

スポンサーリンク

送信されてきたデータの扱いはカスタマイズ次第

さて、前述の通り、CreateView においては、フォームからのデータの送信があった際に送信されてきたデータからインスタンスを生成し、それをレコードとしたデータベースへの新規登録が行われるようになっています。また、UpdateView においては、既にデータベースに登録済みのレコードを、送信されてきたデータに基づいて生成したレコード(インスタンス)で上書きする処理が行われるようになっています。ここで重要なポイントは、フォームから送信されてきたデータの扱いはクラスによって決まっているという点になります。

送信されてきたデータの扱いがクラスによって決まっている様子

また、これらのクラスでは、上記の処理が完了した後に特定の URL へのリダイレクトレスポンスの返却が行われるようになっています。

では、FormView ではフォームからのデータの送信があった際にどんな処理が行われるのでしょうか?

結論を言うと、特定の URL へのリダイレクトレスポンスの返却のみが行われます。つまり、フォームから送信されてきたデータは無視されます。これだとフォームを扱う意味がありませんね…。こんな View のサブクラス がなぜ用意されているのでしょうか…。

この理由も単純で、FormView は開発者からカスタマイズされることを前提とした作りとなっているからになります。もちろん、他の View のサブクラス もカスタマイズされることを前提とした作りとなっているのですが、FormView の場合はカスタマイズが必須で、特に form_valid というメソッドのオーバーライドが必須となっています。

そして、このメソッドをオーバーライドして内部の処理を実装してやることで、開発者が自由にフォームから送信されてきたデータの扱いを決めることができます。具体的なオーバーライドの手順等は後述で解説していきますが、FormView 自体はフォームを扱う View のサブクラス であるもののフォームから送信されてきたデータは基本的に無視されるようになっていること、さらにはカスタマイズ次第でフォームから送信されてきたデータの扱いを開発者自身が自由に決めることができることは覚えておくと良いと思います。

DeleteViewの場合、送信されてきたデータの扱いがカスタマイズ次第であることを示す図

補足しておくと、CreateViewUpdateView でもカスタマイズによって送信されてきたデータの扱いを変更させることは可能です。例えば CreateView でも、フォームから送信されてきたデータのレコードとしてのデータベースへの新規登録を行わないようにすることも可能です。

ただ、送信されてきたデータの扱いを変更してしまうと CreateViewUpdateView の役割が曖昧になってしまいますし、これらをわざわざ利用する意味も無くなってしまうことになるため、ここに関しては変更せずに利用するケースの方が多いと思います。重要なのは、各 View のサブクラス の役割を理解し、それに応じて使い分けることです。

FormView でのクラスベースビューの作り方

では、次は FormView でクラスベースビューを作成する手順について説明していきます。

FormView でのクラスベースビューの作り方に関しては、ここまで紹介してきた CreateViewUpdateView の場合とほぼ同様となります。

つまり、views.pyFormView を継承するクラスを定義し、そのクラスでクラス変数やメソッドを定義して FormView 側で定義されているクラス変数の上書きメソッドのオーバーライドを行なうことで、ビューを作成していきます。これにより、FormView の特徴を活かしながら自身のウェブアプリに応じたビューにカスタマイズしていくことが可能となります。

クラスベースビューの作り方の説明図

また、これも CreateViewUpdateView と同様に、FormView ではリクエストに応じて get メソッドや  post メソッドが実行されるようになっており、FormView のクラス変数や他のメソッドは、これらの get メソッドおよび post メソッドから利用されるようになっています。したがって、FormView のサブクラス側でクラス変数やメソッドを上書き・オーバーライドしてやれば、この get メソッドと post メソッドの動作を変化させることができます。

FormViewのgetメソッドやpostメソッドからクラス変数や他のメソッドが利用される様子

FormView のサブクラスを定義する上で基本的に必須になるのは下記の3つとなります。

  • 利用するフォームクラスを指定する
  • 利用するテンプレートファイルを指定する
  • form_valid のオーバーライドを行う

利用するフォームクラスを指定する

まず、FormView はフォームを扱うために利用する View のサブクラス であり、get メソッドではフォームを表示するために、また post メソッドではフォームから送信されてきたデータを扱うためにフォームクラスのインスタンスが利用されることになります。そして、そのフォームクラスを指定するのがクラス変数 form_class の役割となります。

そのため、FormView のサブクラスを定義する際にはクラス変数 form_class を定義し、この form_class にはフォームクラスを指定しておく必要があります。クラス変数 form_class を定義していなかった場合、get メソッドや post メソッドが実行された際に下記のような例外が発生することになります。

'NoneType' object is not callable

利用するフォームを指定する必要があるのは CreateViewUpdateView の場合でも同様なのですが、これらの場合は form_class 以外のクラス変数の定義でもクラスで扱うフォームを指定することが可能でした(modelfields)。それに対し、FormView の場合はフォームクラスを指定するクラス変数は form_class の一択となります。

また、CreateViewUpdateView の場合は form_class にはモデルフォームクラス(forms.ModelForm を継承するクラス)を指定する必要がありましたが、FormView の場合はモデルフォームクラスでも単なるフォームクラス(forms.Form を継承するクラス)でも問題ありません。

スポンサーリンク

利用するテンプレートファイルを指定する

また、FormView のサブクラスではクラス変数 template_name の定義も必須となります。この template_name には、基本的には FormView のサブクラスが GET メソッドのリクエストを受け取った時に利用するテンプレートファイルのパスを指定します。

そして、フォームを扱う場合、GET メソッドのリクエストを受け取った時にはフォームを表示することになりますので、そのパスにはフォームが表示可能なテンプレートファイルを用意しておく必要があります。また、詳細は FormView が生成するコンテキスト で解説しますが、FormView が GET メソッドのリクエストを受け取った時に生成するコンテキストでは、form キーにフォームクラスのインスタンスがセットされるようになっています。つまり、FormView を利用してフォームを扱うビューを実現する場合、少なくとも下記のような form タグを含むテンプレートファイルを用意しておく必要があります。

フォームの表示
<form action="送信先のURL" method="post">
    {% csrf_token %}
    <table>{{ form.as_table }}</table>
    <p><input type="submit" value="送信"></p>
</form>

上記はフォームのフィールドをテーブル形式で出力することを前提としたものになっているため、出力形式に合わせて変更する必要があるので注意してください。このあたりの Django におけるフォームの表示に関しては下記ページで詳しく解説していますので、必要に応じて下記ページを参照していただければと思います。

Djangoのフォームの解説ページアイキャッチ 【Django入門5】フォームの基本

CreateViewUpdateView の場合、template_name を定義しなかった場合はクラスによって設定されているデフォルトのパスのテンプレートファイルが利用されることになります。なので、template_name を定義しなくてもフォームの表示等を行うことは可能でした。それに対し、FormView のサブクラスにはデフォルトのテンプレートファイルのパスが存在しないため、テンプレートファイルを利用するためにはクラス変数として template_name を定義しておくことが必須となります。そして、template_name を定義しなかった場合は get メソッドが実行される際に例外が発生することになります。

form_valid のオーバーライドを行う

さらに、送信されてきたデータの扱いはカスタマイズ次第 で説明したように、FormView に関してはフォームから送信されてきたデータを扱う処理を開発者自身が実装する必要があります。これを行わないとフォームからデータが送信されてきても無視されてしまうことになります。

そして、このフォームから送信されてきたデータを扱う処理は FormView のサブクラスの form_valid メソッドに実装することになります。この form_valid メソッドは FormView で定義されていますので、要は form_valid メソッドのオーバーライドを行ないます。

この form_valid メソッドは FormView だけでなく、CreateViewUpdateView などのフォームを扱う View のサブクラス 全般に用意されているメソッドです。そして、このメソッドはフォームから送信されてきたデータの妥当性の検証結果が OK の場合の処理を実装するメソッドとなります。

CreateViewUpdateView の場合、これらのビューで実現することは明白で、CreateView の場合は、それが「フォームから送信されてきたデータをレコード(インスタンス)としてデータベースに新規登録すること」になります。そのため、CreateViewform_valid メソッドではレコードのデータベースへの新規登録処理が記述されています。そして、その後に特定の URL に対するリダイレクトレスポンスを返却する処理が記述されています。

CreateViewのform_validに記述されている処理

UpdateView の場合もほぼ同様ですが、UpdateViewform_valid メソッドではレコードの新規登録処理ではなく、データベースに登録済みのレコードの更新処理が記述されています。

これらに対し、FormView の form_valid メソッドには特定の URL に対するリダイレクトレスポンスを返却する処理しか記述されていません。そのため、前述の通り FormView ではフォームからデータが送信されてきたとしても、そのデータに対する処理は一切行わないようになっています。正直これだとユーザーに対してフォームを表示してデータの入力受付を行う意味がないですね…。

FormViewのform_validに記述されている処理

つまり、FormView のサブクラスで実現されるビューは、form_valid をオーバーライドしない限りフォームを扱う意味のないビューしか実現できないことになります。

そのため、FormView はサブクラス側で form_valid メソッドを開発者自身が定義してオーバーライドすることを前提とした View のサブクラス と考えて良いと思います。そもそも、FormView の場合、CreateViewUpdateView のようにビューで実現することが明白ではなく、実現することが曖昧な View のサブクラス となっています。違う言い方をすれば、FormView は単にフォームを扱うための View のサブクラス であり、そのフォームの利用目的は開発するウェブアプリによって異なります。なので、”開発するウェブアプリに応じて自由に form_valid メソッドを開発者が定義してください” というスタンスの View のサブクラス となっているのだと思います。

FormViewのサブクラス側でform_validを定義してオーバーライドする様子

そのため、FormView の場合、開発者自身が form_valid メソッドを定義し、ウェブアプリの動作に合わせた処理を実装する必要があります。もちろん、特定の URL に対してリダイレクトを行うだけで良いのであれば form_valid メソッドを定義してオーバーライドする必要もないのですが、それだとフォームによってユーザーからのデータの送信を受け付ける意味もありませんので、基本は form_valid メソッドを定義してオーバーライドすることは必須であると考えて良いです。

FormView のクラス変数

では、次は FormView で定義されているクラス変数の紹介を行なっていきます。これらのクラス変数を FormView を継承するクラスで定義し直して上書きすることでクラスの動作のカスタマイズを行うことが可能となります。

FormView の場合、GET メソッドのリクエストを受け取った際に get メソッドが実行され、GET メソッドのリクエストを受け取った際に post メソッドが実行されることになるため、これらの get メソッドや post メソッドの動作を変化させることを目的にクラス変数の定義を行なっていくことになります。

スポンサーリンク

FormView のクラス変数の一覧

この FormView で定義されるクラス変数には下記のようなものが存在します。

各リンクをクリックすれば括弧内に示した View のサブクラス の解説ページにジャンプするようにしています。基本的に、フォームを扱うための設定を行うクラス変数に関しては CreateView の解説ページ、その他のクラス変数に関しては ListView の解説ページにジャンプするようにしています。

FormView でのクラスベースビューの作り方 でも説明したように、FormView ではクラス変数 form_classtemplate_name の定義が基本的に必須になるので注意してください。POST リクエストを受け取り、さらにフォームから送信されてきたデータの妥当性の検証結果が OK の場合にリダイレクトを行うようにしたい場合は success_url の定義も必須となります。

また、CreateViewUpdateView、さらには ListViewDetailView 等には共通してクラス変数 model が存在しますが、FormView にはクラス変数 model は存在しません。model は、モデルクラスを指定することで操作対象のデータベースのテーブルを各クラスに指定する役割があったのですが、FormView はデータベースの操作を行うことは前提となっていないためクラス変数 model が存在しません。同様の理由で、FormView にはデータベースに発行するクエリを指定する queryset なども存在せず、上記で挙げた View のサブクラス に比べると FormView におけるクラス変数の数は少なくなっています。

FormView のメソッド

次に FormView の持つメソッドを紹介していきます。FormView を継承したクラスを定義し、そのクラスで FormView の持つメソッドをオーバーライドしてやることで FormView とは異なる動作のクラスを実現することができるようになります。

FormView のメソッド一覧

FormView の持つメソッドの一覧は下記のようになります。あくまでもクラスのカスタマイズ目的でオーバーライドを行う可能性のあるものを挙げており、as_view などのカスタマイズは行わないであろうメソッドは省略しています。

また、FormViewget メソッドと post メソッドを持っており、メソッドが GET のリクエストを受け取った場合は get が実行され、メソッドが POST のリクエストを受け取った場合は post が実行されようになっています。そして、これらの getpost の中から各種メソッドが実行されるようになっています。

  • get:リクエストのメソッドが GET の場合の処理を実行する
  • post:リクエストのメソッドが POST の場合の処理を実行する
  • get_context_data:テンプレートに渡すコンテキストを生成する
  • get_form:フォームクラスのインスタンスを取得する
  • get_form_class:フォームクラスを取得する
  • get_form_kwargs:フォームクラスのコンストラクタに指定するパラメータを取得する
  • get_initialinitial への指定値を取得する
  • get_prefixprefix への指定値を取得する
  • form_valid:特定の URL へのリダイレクトレスポンスを返却する(妥当性の検証が OK の時に実行される)
  • form_invalid:再度フォームを表示する(妥当性の検証が NG の時に実行される)
  • get_success_url:リダイレクト先の URL を取得する
  • get_template_names:テンプレートファイルの名前を取得する
  • render_to_response:レスポンスを返却する

これも FormView でのクラスベースビューの作り方 でも説明したように、FormViewform_valid ではリダイレクトレスポンスの返却のみが行われることになるため、フォームからユーザーがデータを送信してきたとしても、そのデータは無視されることになります。つまり、FormViewform_valid をそのまま利用するとフォームを扱う意味が無いことになります。

なので、FormView のサブクラスを “フォームを扱う意味があるクラス” とするためには、この form_valid に関してはオーバーライドが必須となります。

ちなみに、送信されてきたデータの妥当性の検証結果が NG の場合は form_invalid メソッドが実行されることになり、このメソッドでは再度フォームの表示を行うための処理が行われます。この処理自体は CreateView 等の他のフォームを扱う View のサブクラス と同じであり、送信されてきたデータの妥当性の検証結果が NG の場合の典型的な処理が実現されているため、特に変更する必要はないと思います(もちろん必要に応じて変更しても良いです)。 

スポンサーリンク

form_valid のオーバーライド

ということで、ここでは form_valid のオーバーライドについて解説しておきたいと思います。

form_valid の引数と返却値

まず、form_valid のオーバーライドは、FormView のサブクラス側で form_valid メソッドを定義することで実現することができます。ただし、引数や返却値は元々の FormView の form_valid メソッドに合わせる必要があります。

この FormView の form_valid メソッドは下記のように定義されています。

FormViewのform_valid
def form_valid(self, form):
    """If the form is valid, redirect to the supplied URL."""
    return HttpResponseRedirect(self.get_success_url())

ご覧の通り、引数として selfform を受け取るようになっており、返却値は HttpResponseRedirect のインスタンスとなっています。ただし、返却値に関しては、HttpResponse のサブクラスであれば、別のクラスのインスタンスでも問題ありません。

上記の HttpResponseRedirect に関しても HttpResponse のサブクラスなので、リダイレクトを行いたい場合は、上記を真似て HttpResponseRedirect のインスタンスを返却するようにしてやれば良いと思います。

また、HttpResponse のサブクラスのインスタンスを返却する関数やメソッドも Django フレームワークには多数用意されていますので、その実行結果を返却するのでも良いです。例えば先ほど示したメソッド一覧の中にある render_to_response に関しても HttpResponse のサブクラスのインスタンスを返却するメソッドとなっています。また、関数ベースビューの時に利用することの多い renderHttpResponse のサブクラスのインスタンスを返却する関数となります。

ここまでをまとめると、form_valid のオーバーライドを行う場合、まずは FormView のサブクラスに下記のような形式の form_valid を定義してやれば良いことになります。

form_validのオーバーライド
def form_valid(self, form):
    # 略
    return HttpResponseのサブクラスのインスタンス

このように form_valid を定義しておけば、FormView のサブクラスが POST リクエストを受け取り、受信したフォームの各種フィールドの値が妥当であると判断された際に、FormView ではなく、FormView のサブクラスの form_valid が実行されるようになります。さらに、この form_valid の返却値に応じたページ表示が行われることになります。

フォームから送信されてきたデータを取得する

続いて、form_valid 内部の処理を実装していきます。

この form_valid のオーバーライドを行う目的は「フォームから送信されてきたデータを扱う」ことになります。どのように扱うかはウェブアプリによって異なりますが、フォームから送信されてきたデータを扱うためには、まずフォームから送信されてきたデータから各種フィールドの値を取得することが必要となります。

したがって、form_valid には、フォームから送信されてきたデータから各種フィールドの値を取得する処理を記述する必要があります。フォームから送信されてきたデータは、妥当性の検証が行われた後に、引数 form のデータ属性 cleaned_data に辞書データとしてセットされることになります。なので、form_valid では、その form.cleaned_data から各種フィールドの値を取得することが可能です。

例えば下記のように forms.py でフォームクラス BmiForm を定義した場合、

フォームクラスの定義
from django import forms

class BmiForm(forms.Form):
    height = forms.FloatField()
    weight = forms.FloatField()

下記のような処理によって、form_valid でフォームの各種フィールドに入力された値を取得することができます。

各種フィールドのデータの取得
def form_valid(self, form):
    height = form.cleaned_data.get('height')
    weight = form.cleaned_data.get('weight')

    # 略
    return HttpResponseのサブクラスのインスタンス

フォームから送信されてきたデータを扱う

上記のような処理によって、ユーザーがフォームの各種フィールドに入力した値を取得することができますので、後は、これらのデータを利用して目的に応じた処理を実行してやれば良いことになります。

どんな処理を行うのかはウェブアプリによって異なりますが、取得した値を利用して何らかの処理を行い、その結果を埋め込んだ HTML をレスポンスとして返却するケースが多いのではないかと思います。

FormViewの処理の流れの例

例えば、先ほどの BmiForm であれば、height フィールドと weight フィールドの値を取得してやれば BMI の計算が可能となりますので、その計算を行なって結果を HTML に埋め込んでレスポンスを返却してやることで、ウェブブラウザで表示するページに BMI が出力されるようになります。そして、これは FormView のサブクラスで下記のように form_valid を定義してやることで実現することができます。

BMIの表示
def form_valid(self, form):
    height = form.cleaned_data.get('height')
    weight = form.cleaned_data.get('weight')
    
    bmi = weight / ((height / 100) * (height / 100))

    context = {
        'bmi': bmi
    }
    
    return render(self.request, 'アプリ名/bmi.html', context)

基本的には、関数ベースビューの時と同様に render 関数を実行して HTML を生成し、render 関数の返却値である、その HTML をボディとする HttpResponse のサブクラスのインスタンスを返却しているだけです。'アプリ名/bmi.html' に、コンテキストの 'bmi' を参照するための {{ bmi }} が記述されたテンプレートファイルを用意しておけば、height フィールドと weight フィールドに入力された値から計算した BMI 値が出力されるページが表示されることになります。

前述の通り、form_valid メソッドでは HttpResponse のサブクラスのインスタンスを返却することが必須となります。逆に言うと、これさえ守れば、後は自由に form_valid メソッドを実装して問題ありません。

また、FormView のサブクラスではクラス変数 template_name を定義することになるため、HTML 生成時に利用するテンプレートファイルのパスとして template_name を指定することも可能です。ですが、この template_name は、基本的には FormView のサブクラスが GET リクエストを受け取ってフォームを表示する時に利用するテンプレートファイルのパスを定義するクラス変数となります。なので、このパスにはフォーム出力用のテンプレートファイルが設置されることになります。それに対し、POST リクエストを受け取った時に実行される form_valid メソッドで利用するテンプレートファイルとしては、フォームから送信されてきたフィールドの値を用いた何らかの処理結果を出力することが多く、その場合はフォーム出力用ではなく結果出力用のテンプレートファイルが必要となります。

つまり、GET リクエストを受け取った時と POST リクエストを受け取った時とでは出力するデータが異なるため、テンプレートファイルとしても別々のものを用意しておく必要があります。もしくは、template_name で指定するパスに、フォーム出力と結果出力の両方を兼ね備えたテンプレートファイルを設置しておくのでも良いです。この場合は用意するテンプレートファイルが1つで済みます。

ここまで説明してきたように、form_valid メソッドは開発するウェブアプリに合わせて自由に定義することが可能ですが、「FormViewform_valid と引数・返却値を合わせた form_valid を定義」し、さらに「フォームから送信されてきた各種フィールドに入力されたデータを引数 form より取得」し、「その取得したデータを扱って何らかの処理を行う」という流れは共通になりますので、これに関しては是非覚えておいてください。

FormView が生成するコンテキスト

続いて FormView が生成するコンテキストについて説明しておきます。

FormViewget_context_data メソッドの中で生成するコンテキストには下記の要素が含まれます。この “フォームクラス” はクラス変数 form_class で指定したクラスとなります。

  • 'form' : フォームクラスのインスタンス

このように、FormView が生成するコンテキストにはフォームクラスのインスタンスしかセットされていません。

他のデータをコンテキストにセットしてテンプレートファイルから参照できるようにしたい場合は、ListView の解説ページの extra_context で説明しているクラス変数 extra_context を定義したり、get_context_data のオーバーライドを行なったりしてコンテキストの要素を追加する必要があります。

また上記のように get_context_data メソッドで 'form' キーしか持たないコンテキストが生成されるのは、get_context_data メソッドの引数に何も指定しなかった場合の話となります。get_context_data メソッドの引数にキーワード引数を指定してやれば、そのキーワード引数で指定したキー名・値を持つ要素がコンテキストに追加されるようになりますので、こういった方法でもコンテキストの要素の追加を行うことができます。

この辺りの実例は、次の FormView の利用例 で紹介します。

FormView の利用例

最後に、ここまでの説明のまとめとして、FormView を継承するクラスでのビューの作成例を示していきたいと思います。

スポンサーリンク

プロジェクトとアプリの作成

最初にいつも通りプロジェクトを作成していきしょう!。

まず、適当な作業フォルダに移動した後、下記コマンドでプロジェクトを作成します。今回はプロジェクトを formviewproject としています。

% django-admin startproject formviewproject

続いてアプリを作成していきます。先ほどのコマンドの実行で formviewproject というフォルダが作成されたはずなので、この formviewproject に下記の cd コマンドで移動します。

% cd formviewproject

続いて、下記コマンドを実行してアプリを作成します。今回はアプリを bmi としています。

% python manage.py startapp bmi

また、先ほど cd コマンドで移動後のフォルダの下にも formviewproject というフォルダがあるはずなので、そのフォルダの下にある settings.py を開き、下記のように INSTALLED_APPS のリストを変更します。これにより、アプリ bmi がプロジェクトに登録されることになります。

settings.pyの変更
INSTALLED_APPS = [
    'bmi', # 追加
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

フォームの定義

次はフォームを定義していきます。今回は、身長(cm)と体重(kg)の入力を受け付けるため、下記のような height フィールドと weight フィールドを持つ BmiForm を定義したいと思います。定義先のファイルは bmi/forms.py となりますが、このファイルは存在しないためファイルを新規作成してから下記の様に中身を変更するようにしてください。

forms.py
from django import forms

class BmiForm(forms.Form):
    height = forms.FloatField()
    weight = forms.FloatField()

クラスベースビューの作成

続いて、本題のクラスベースビューの定義を行っていきます。今回は、ここまでの説明の通り FormView を継承するクラスを定義し、そのクラスをクラス変数の定義とメソッドのオーバーライドによってカスタマイズしていきます。

今回は、下記のように views.py を変更し、FormView を継承するクラスとして BmiView 定義したいと思います。

views.py
from django.views.generic import FormView
from .forms import BmiForm

class BmiView(FormView):
    form_class = BmiForm
    template_name = 'bmi/bmi_form.html'

    def form_valid(self, form):
        height = form.cleaned_data.get('height')
        weight = form.cleaned_data.get('weight')

        bmi = weight / ((height / 100) * (height / 100))

        return self.render_to_response(self.get_context_data(bmi=bmi))

form_class の定義

BmiView ではクラス変数 form_class を定義して BmiForm を指定しているため、BmiView でフォームを扱う際には先ほど forms.py に定義した BmiForm が利用されることになります。

より具体的にいうと、 BmiView が  GET リクエストを受け取った際には get メソッドが実行され、その中で BmiForm のインスタンスが生成されてコンテキストにセットされることになります。なので、テンプレートファイルから BmiForm のインスタンスを参照し、ページに height フィールドと weight フィールドを持つフォームを表示することができるようになります。

さらに、BmiView が  POST リクエストを受け取って post メソッドが実行される際にも BmiForm のインスタンスが生成されることになります。post メソッド実行時は、ユーザーがフォームに入力した各種フィールドのデータがセットされた状態のインスタンスが生成されるようになっており、このインスタンスを利用して、送信されてきたデータの妥当性の検証やフォームの再表示、さらには form_valid メソッドの処理が実行されることになります。

このように、クラス変数 form_class に指定したフォームクラスは様々な場面で利用されます。

template_name の定義

また、BmiView ではクラス変数 template_name を定義して 'bmi/bmi_form.html' を指定しているため、BmiView が HTML を生成するような際には、このパスのテンプレートファイルが利用されることになります。この bmi/bmi_form.html は後述の説明の中で作成していきます。

form_valid の定義

さらに、BmiView では form_valid メソッドの定義を行なっています。form_valid のオーバーライドを行う 等でも説明したように、FormView のサブクラスでは、この form_valid メソッドの定義が基本的には必須となります。

上記の form_valid メソッドのポイントは、返却値が「render_to_response メソッドの返却値」になっているという点になります。まず、form_valid のオーバーライド で説明したように、form_valid メソッドは HttpResponse のサブクラスのインスタンスを返却する必要があります。この render_to_response メソッドの返却値は HttpResponse のサブクラスのインスタンスであるため、form_valid からも HttpResponse のサブクラスのインスタンスが返却できることになります。

また、この render_to_response メソッドの引数には self.get_context_data(bmi=bmi) が指定されています。get_context_dataFormView によって生成されたコンテキストを取得するメソッドになります。FormView が生成するコンテキスト でも説明したように、基本的には FormView によって生成されたコンテキストにはフォームクラスのインスタンスしか含まれません。ですが、get_context_data にキーワード引数を指定することで、そのコンテキストに要素を追加することが可能です。上記のように bmi=bmi を指定すれば、コンテキストには値が「bmi 変数の値」である 'bmi' キーの要素が追加されることになります。これにより、テンプレートファイルから bmi 変数の値が参照可能となります。  

さらに、この render_to_response では HTML の生成が行われるのですが、この HTML はクラス変数 template_name で指定されたパスのテンプレートファイルを利用して生成されることになります。したがって、クラス変数 template_name で指定されるパスのテンプレートファイルは、フォーム出力と結果出力(BMI 値の出力)の両方を兼ね備えたものである必要があります。このテンプレートファイルの作り方については後述で解説します。

スポンサーリンク

ビューと URL とのマッピング

次は views.py で定義したビューと URL とのマッピングを行なっていきます。

まず、formviewproject フォルダの下にある urls.py を下記のように変更してください。

formviewproject/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('bmi/', include('bmi.urls')),
]

続いて、bmi フォルダの下に urls.py を新規作成し、中身を下記のように変更してください。

bmi/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.BmiView.as_view(), name='bmi'),
]

テンプレートの作成

次はテンプレートファイルを作成していきます。ソースコードの変更としては、これが最後となります。

最初にテンプレートファイルを設置するのに必要となるフォルダを準備しておきましょう。まずアプリフォルダ(bmi フォルダ)の中に templates フォルダを作成してください。次に、その templates フォルダの中に bmi フォルダを作成します。これでフォルダの準備は完了です。

今回 views.py で定義した BmiView ではクラス変数 template_name を定義して 'bmi/bmi_form.html' を指定していますので、BmiViewGET リクエストを受け取った時には、先ほど作成した bmi フォルダの中の bmi_form.html が利用されることになります。また、form_valid メソッドのオーバーライドにより、POST リクエストを受け取った時にも同じテンプレートファイルが利用されることになります。

そのため、まずは先ほど作成した bmi フォルダの中に bmi_form.html を新規作成する必要があります。そして、このファイルの中身を下記のように変更して保存してください。これでテンプレートファイルの作成は完了になります。

bmi_form.html
<!doctype html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Example of FormView</title>
</head>
<body>
    <main>
        <h2>BMIの計算</h2>
        {% if bmi %}
        <p>あなたのBMIは「{{ bmi }}」です</p>
        {% endif %}
        <form action="{% url 'bmi' %}" method="post">
            {% csrf_token %}
            <table>{{ form.as_table }}</table>
            <p><input type="submit" value="送信"></p>
        </form>
    </main>
</body>
</html>

bmi の参照

ポイントを説明しておくと、まず、このテンプレートファイルでは bmi と form という変数を参照し、結果出力(BMI 値の出力)とフォーム出力の両方が行えるように作成しています。

bmi の参照箇所は下記で、コンテキストに 'bmi' キーの要素が存在する場合のみ下記の if が成立し、bmi の値が出力されるようになっています。GET リクエスト時は FormView が生成するコンテキスト で説明したようにコンテキストには 'bmi' キーの要素が存在しないため bmi の値は出力されないことになります。それに対し、POST リクエスト時には form_valid のオーバーライドによってコンテキストに 'bmi' キーの要素を追加するようにしているため bmi の値が出力されることになります。

bmiの参照
{% if bmi %}
<p>あなたのBMIは「{{ bmi }}」です</p>
{% endif %}

form の参照

そして、その bmi の値はフォームの height フィールドと weight フィールドに入力された数値から計算するようになっています。そして、この height フィールドと weight フィールドの入力欄を表示するために form を参照するようにしています。

FormView が生成するコンテキスト で説明したように、formBmiView ではクラス変数 form_class で指定した BmiForm のインスタンスであり、{{ form.as_table }} によってフォームをテーブル形式で出力しています。BmiForm では height フィールドと weight フィールドが定義されているため、これにより HTML 生成時には heightweight の入力フィールドがタグとして HTML に追加されることになります。

また、{{ form.as_table }} の下の行に存在するタグはボタン要素に対応するもので、このボタンをクリックすれば form タグの action 属性に指定した URL に POST リクエストが送信されることになります。そして、この action 属性には、urls.py によって BmiView にマッピングされた URL が指定されることになるため、BmiView に対して POST リクエストが送信されることになります。

そして、POST リクエストを受け取った BmiView は、フォームから送信されてきたデータの妥当性の検証を行い、その結果が OK の際に form_valid が実行されて height フィールドと weight フィールドに入力された数値から bmi を算出し、それをコンテキストの 'bmi' キーの値にセットした状態でテンプレートファイルからの HTML 生成が行われます。この際には、コンテキストに 'bmi' キーの要素が存在するため、下記によって bmi の出力が行われることになります。

bmiの参照
{% if bmi %}
<p>あなたのBMIは「{{ bmi }}」です</p>
{% endif %}

ということで、この bmi_form.html では結果の出力とフォームの出力の両方が実現できるようになっています。今回は1つのテンプレートファイルのみでこれらを実現しましたが、form_valid のオーバーライド で説明したように、結果の出力用のテンプレートファイルとフォームの出力用のテンプレートファイルとを別々に用意し、それらのテンプレートファイルをメソッドに応じて使い分けるようにするのでも問題ありません。

動作確認

最後に動作確認を行なっておきましょう!

最初に、manage.py が存在するフォルダで下記コマンドを実行します。

% python manage.py runserver

これにより、開発用ウェブサーバーが起動しますので、ウェブブラウザから作成したアプリのページを表示することが可能となります。

次に下記 URL をウェブブラウザで表示してみてください。

http://localhost:8000/bmi/

この URL をウェブブラウザで表示すれば下図のような heightweight の入力フィールドを持つフォームが表示されるはずです。これは BmiFormGET リクエストを受け取ったことによって表示されるフォームとなります。

GETリクエストを受け取ったFormViewによって表示されるフォーム

表示されたフォームの heightweight の入力フィールドに適当な数値を入力して 送信 ボタンを押せば今度は BmiFormPOST リクエストを受け取り、height フィールドと weight フィールドに入力された数値から bmi が計算され、その計算結果がページに表示されることになります。ちなみに、height フィールドに入力する数値の単位は cm、weight フィールドに入力する数値の単位は kg となります。

POSTリクエストを受け取ったFormViewによって表示される結果

動作確認としては以上となるのですが、ポイントは今回の例ではデータベースの操作が全く行われていない点にあります。CreateViewUpdateView ではデータベースへのレコードの保存が行われますし、ListViewDetailView ではデータベースからのレコードの取得が行われます。

それに対し、FormView の場合はデータベースの操作は必ずしも行われず、その場合はモデルクラスの定義やビューから利用するモデルクラスの設定等も不要になります。こんな感じで、データベースの操作を伴わないようなビューを実現する時に便利なのが FormView となります。

ただ、今回示した例はデータベースの操作は伴わないものになっていますが、実装によってはデータベースの操作を行うことも可能です。もちろん CreateViewUpdateView 等を利用する方がデータベースの操作を行うのが楽なのですが、FormView は開発者の実装によって様々なビューを実現可能な汎用性の高い View のサブクラス であることは覚えておくと良いと思います!

スポンサーリンク

まとめ

このページでは、FormView および FormView を継承したクラスベースビューの作り方について解説しました!

FormView はフォームを扱うビューを実現するための View のサブクラス であり、このクラスを継承することで、様々なフォームを扱うページを簡単に実現できます。

他のページで解説を行なっている CreateViewUpdateView 等とは異なり、FormView はデータベースの操作が行われることが前提となっていない View のサブクラス となります。そのため、データベースへの操作を伴わないようなフォームを扱うビューを実現するのに向いています。実装によっては FormView のサブクラスでデータベースへの操作を行うことも可能あり、汎用性の高い View のサブクラス となります。

データベースの操作が不要なウェブアプリやビューを開発していく上で便利な View のサブクラス となりますので、是非 FormView についても使いこなせるようになっておきましょう!

また、このサイトでは他の View のサブクラス についても説明していますので、他のページも是非読んでみてください!

DjangoのListViewの解説ページアイキャッチ 【Django】ListViewの使い方(クラスベースビューでの一覧リストページの実現) DjangoのDetailViewの解説ページアイキャッチ 【Django】DetailViewの使い方(クラスベースビューでの詳細ページの実現) CreateViewでのクラスベースビューの実現方法解説ページアイキャッチ 【Django】CreateViewの使い方(クラスベースビューでの新規登録ページの実現) UpdateViewでのクラスベースビューの実現方法解説ページアイキャッチ 【Django】UpdateViewの使い方(クラスベースビューでのレコード更新ページの実現) DeleteViewでのクラスベースビューの実現方法解説ページアイキャッチ 【Django】DeleteViewの使い方(クラスベースビューでのレコード削除ページの実現) LoginViewの使い方の解説ページアイキャッチ 【Django】LoginViewの使い方(クラスベースビューでのログインの実現) [kanren id=”25507z

同じカテゴリのページ一覧を表示