【Django】FormViewでform_validをオーバーライドしてPOST時の動作を追加する

当ページのリンクには広告が含まれています。
FormViewのform_validの使い方

FormViewを使用することで、Djangoでフォームを簡単に実装することができます。

しかし、FormViewのデフォルトのPOST動作では、クラス変数self.success_urlに指定したURLへリダイレクトするだけの動作となっていて、テンプレートにコンテキスト情報を渡してレンダリングする、といったことができません。

そこで今回は、FormViewの実際の実装を確認しつつ、POST時の動作を任意の処理に置き換えるためにはどうすればいいのかを実装例を交えて解説していきます

ちなみに、Djangoに必要なHTML/CSS、JavaScriptなどのWeb開発系言語はという学習サイトで無料で学習できるのでおすすめですよ!

\無料プランを無期限で試す/

メールアドレスだけで10秒で登録!

また、以下の記事ではDjangoが学べるおすすめのプログラミングスクールをDjangoを学ぶ目的別に紹介しています

Djangoの習得には幅広い知識が必要で、独学では大変な部分も多いと思いますので、気になる方はよければ覗いてみてください。

目次

【結論】form_validメソッドをオーバーライドする

まずは結論を簡単に説明しておきます。

FormViewでは、POST動作時には最終的にform_valid()というメソッドが呼び出されて、クラス変数self.success_urlに設定されたページを表示するような作りになっています。

なので、POST時の動作を制御するには、このform_valid()メソッドをオーバーライドすればいいです。

一方で、form_validメソッドの引数は、送信されたフォームの情報だけなので、render関数でレンダリングする際はrequest引数をどうすればいいのか?という問題が出てきます。

これの解決法としては、クラス変数のself.requestをrequestとして用いることで解決できます

この点についても以下で詳しく解説します。

FormViewとは?

まずは簡単にFormViewの説明と、使い方を説明します。

すでに知っている、と言う方は読み飛ばしてくださいね。

FormViewを使用するメリット

まずは、FormViewを使用するメリットを紹介します。

djangoでformを使用するには、通常のview関数による実装や、ViewやTemplateViewなどのようなクラスベースビューによる実装が可能です。

しかしながら、FormViewを使うことでそれらの他の方法よりも簡単にフォームを使ったviewを実装することができます

FormViewが他のクラスベースビューと違う点として、以下の機能があります。

  • クラス変数self.form_classにフォームクラスを設定できる
  • フォームPOST時に、自動でフォームのバリデーションを行う
  • バリデーション成功時に、self.success_urlに自動でリダイレクトする

まさにフォームに特化したクラスベースビューになっています。

特にうれしいのがフォームのバリエーションが自動的に行われる部分です。

他のクラスベースビューを使用した場合はpostメソッドをオーバーライドして、is_valid()メソッドでバリデーションを行う必要がありますが、FormViewの場合は不要となります。

FormViewnの基本的な使い方は以下の記事で紹介しています。

実装例

今回の実装例では、formviewというアプリ名でアプリを作成しています。

最終的に作成するページ

今回最終的に作成するページのイメージを先に紹介しておきます。

名前、年齢のフィールドに値を入力し「POST」ボタンを押すと、その下に文章が現れるようなページです。

POST前
POST後

urls.py

後で定義しますが、views.pyのIndexViewを表示するようにしています。

from django.urls import path
from .views import IndexView


urlpatterns = [
    path('', IndexView.as_view(), name='index')
]

forms.py

名前用の文字列フィールドと、年齢用の数値フィールドを持ったフォームを定義しています。

from django import forms


class MemberForm(forms.Form):
    name = forms.CharField(max_length=100, label = '名前')
    age = forms.IntegerField(label='年齢')

vews.py

今回はFormViewを継承したIndexViewを定義しています。

FormViewに必要な3つの属性(template_name, form_class, success_url)を定義しています。

これだけでも、FormView自体にget, postメソッドがすでに用意されているので動作します。

DjangoのFormViewの実装では、POST時には最終的にself.form_valid()メソッドをコールします

つまり、form_validメソッドをオーバーライドすれば、任意の処理を追加できます

postメソッドをオーバーライドする手もありますが、バリデーションなどの他の処理も自前で用意する、もしくはsuper().post()などで親クラスのpostを1回呼び出してその後に処理を加える必要があります。

であれば、最初からform_validメソッドをオーバーライドしたほうが素直でいいと思いますね。

from django.shortcuts import render
from django.views.generic.edit import FormView
from django.views.generic import TemplateView
from .forms import MemberForm
from django.urls import reverse_lazy


class IndexView(FormView):
    template_name = 'formview/index.html'
    form_class = MemberForm
    success_url = reverse_lazy('index')

    # form_validをオーバーライドする
    def form_valid(self, form):
        introduction = f"私の名前は{form.cleaned_data['name']}です。年齢は{form.cleaned_data['age']}歳です。"
        context = {'form': form, 'intro': introduction}
        return render(self.request, self.template_name, context)

この例ではform_validで最終的にrender()メソッドを呼び出していますが、実際はPOSTでrender()メソッドを呼ぶのは避けた方がいいです。理由は、POSTした後にブラウザの「戻る」や「再読み込み」をするときにPOSTの再送信が行われるためです(ブラウザからエラーも表示されます)。これを防ぐために、基本的にPOST時にはrender()メソッドではなくredirect()などでGETページへリダイレクトすることを推奨します。
(ただしリダイレクトではコンテキスト情報を渡すことができないので、データをモデルに登録するなどしてGETでもデータを取得できるようにする必要があります)

テンプレート

IndexView用のテンプレートでは、フォームを表示します。

formというコンテキストを使うことで、テンプレートフォームクラスを表示させることが可能です。

<form method='POST'>
    {% csrf_token %}
    {{ form }}
    <button type="submit">
        POST
    </button>
</form>
{{ intro }}

ブラウザ表示

POST前
POST後

まとめ

今回はFormViewでPOST時の動作を置き換える方法を紹介しました。

form_validメソッドをオーバーライドすることでフォームのバリデーション後の動作を置き換えることで、POST時の動作を置き換えることができます。

postメソッドを丸ごと置き換えていた方は、ぜひこの方法を試してみてください!

Djangoはまだまだたくさんの機能が盛り沢山です。

このサイトでは、他にもさまざまな解説記事を載せているので、ぜひ参考にしてください!

なお、という学習サイトでは、DjangoをはじめPythonやHTML/CSS/JavaScriptを無料で学ぶことができます!

メールアドレスだけで簡単に無料登録できるのでぜひ覗いてみてくださいね。

\無料プランを無期限で試す/

メールアドレスだけで10秒で登録!

また、独学だけでなく、人から教えてもらうというのも大切です。

以下の記事ではDjangoが学べるプログラミングスクールを、目的別におすすめを紹介していますので、こちらもぜひご検討ください。

DjangoのFromViewの実装

ここからは、実際のDjangoにおけるFormViewの実装を見ながらPOST時の動作を確認しましょう。

FormViewの実装は、こちらのGithubで確認可能です。

実装を追いかけなくてもいい、と言う人は最後のリダイレクト操作の変更する方法まで飛ばして下さい!

実装

まず初めに、FormViewの実装を見て見ます。

といっても、FormViewは2つのクラスを継承しているだけです。

ついでにFormViewが継承しているBaseFormViewも載せますが、こちらはFormMixin、ProcessFormViewを継承しているだけです。

class FormView(TemplateResponseMixin, BaseFormView):

class BaseFormView(FormMixin, ProcessFormView):

post時の動作は、BaseFormViewの親クラスであるProcessFormViewに以下のように定義されています。

class ProcessFormView(View):
    (中略)
    def post(self, request, *args, **kwargs):
        form = self.get_form()
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

上記メソッドでは、内部でget_form、form_validメソッドを呼び出しています。

これらのメソッドは、BaseFormViewのもうひとつの親クラスのFormMixinに定義されています。

class FormMixin(ContextMixin):
    initial = {}
    form_class = None
    success_url = None
    prefix = None

    def get_form_class(self):
        """Return the form class to use."""
        return self.form_class

    def get_form(self, form_class=None):
        """Return an instance of the form to be used in this view."""
        if form_class is None:
            form_class = self.get_form_class()
        return form_class(**self.get_form_kwargs())

    def get_form_kwargs(self):
        """Return the keyword arguments for instantiating the form."""
        kwargs = {
            "initial": self.get_initial(),
            "prefix": self.get_prefix(),
        }

        if self.request.method in ("POST", "PUT"):
            kwargs.update(
                {
                    "data": self.request.POST,
                    "files": self.request.FILES,
                }
            )
        return kwargs

    def get_success_url(self):
        """Return the URL to redirect to after processing a valid form."""
        if not self.success_url:
            raise ImproperlyConfigured("No URL to redirect to. Provide a success_url.")
        return str(self.success_url)  # success_url may be lazy

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

    def form_invalid(self, form):
        """If the form is invalid, render the invalid form."""
        return self.render_to_response(self.get_context_data(form=form))

    def get_context_data(self, **kwargs):
        """Insert the form into the context dict."""
        if "form" not in kwargs:
            kwargs["form"] = self.get_form()
        return super().get_context_data(**kwargs)

では、実装を追いかけて見ましょう。

フォームがPOSTされたとき、まずはProcessFormView.postメソッドがコールされます。

メソッド内では、formという変数に、self.get_formというメソッドの戻り値を設定していますね。

これはFormMixinで定義されていて、引数form_classまたは、クラス変数self.form_classのフォームクラスのインスタンスを返します。

つまり今回の場合だと、ProcessFormView.post内の変数formはMemberFormのインスタンスです

続いて、form.is_validの戻り値で処理が分岐しています。

form.is_validに関してはFormViewではなく、フォーム側のメソッドなので詳しくは触れませんが、フォームの内容が正しいかどうかを判定(バリデーション)して、問題なければTrue、問題があればFalseを返すと思って下さい

postメソッドでは、バリデーション結果が問題なければform_valid()、問題があればform_invalid()メソッドをコールします

form_validもform_invalidもFormMixinクラスのメソッドです。

FormMixin.form_validは、クラス変数self.success_urlに対してリダイレクト(HttpResponceRedirect)しているだけですね。

一方で、FormMixin.form_invalidではrender_to_responceにコンテキスト情報が渡されています。

render_to_responceはFormMixinの親クラスのContextMixinに定義されており(別ファイル)、self.templateに対してレンダリングするメソッドだと思ってください。

つまり、不正なコンテキスト情報を使って、不正を示す文字列とともに再び同じテンプレートページを表示します。

以下は、MemberFormのnameフィールドのmax_lengthを5にして、7文字入力してPOSTした例です。

IndexViewのページ(index.html)が再び表示されていることが分かります。

よかったらシェアしてね!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次