[Django] FormViewについて詳しく見てみる

今回はFormViewについて詳しく見てみましょう。

Githubよりソースコードを拝借します。

django/django https://github.com/django/django/tree/stable/2.2.x

FormView

では、FormViewから

class FormView(TemplateResponseMixin, BaseFormView):
    """A view for displaying a form and rendering a template response."""

二つほどクラスを継承しています。
TemplateResponseMixinBaseFormViewです。
FormViewの中身はBaseFormViewで実装されていそうな雰囲気です。

続いてBaseFormViewを覗いていきます。

TemplateResponseMixinTemplateViewの回で紹介しているのでそちらをご覧ください。

BaseFormView

class BaseFormView(FormMixin, ProcessFormView):
    """A base view for displaying a form."""

こちらも二つほどクラスを継承しています。

それぞれどの部分を実装しているんでしょう?
まずは、ProcessFormViewの方から

ProcessFormView

class ProcessFormView(View):
    """Render a form on GET and processes it on POST."""
    def get(self, request, *args, **kwargs):
        """Handle GET requests: instantiate a blank version of the form."""
        return self.render_to_response(self.get_context_data())

    def post(self, request, *args, **kwargs):
        """
        Handle POST requests: instantiate a form instance with the passed
        POST variables and then check if it's valid.
        """
        form = self.get_form()
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

    def put(self, *args, **kwargs):
        return self.post(*args, **kwargs)

Viewクラスを継承して、それぞれのHttpメソッドに対応する処理が書かれています。
FormViewではgetに加えpost,putがサポートされているようです。

putメソッドは中でpostメソッドを呼んでいるだけですね。

postメソッドでは、get_formメソッドを利用してFormを取得しています。

ProcessFormViewではget_formメソッドは定義されていないので、FormMixinの方で定義されているのでしょう。

取得したFormのis_validを呼んで入力が正しいか検証して、正しければform_validメソッドを、何か不備があればform_invalidメソッドを呼ぶ仕組みになっています。

form_valid,form_invalidともに、ProcessFormViewでは定義されていないので、get_form同様、FormMixinの方で定義されているのでしょう。

いよいよFormMixinです。

FormMixin

class FormMixin(ContextMixin):
    """Provide a way to show and handle a form in a request."""
    initial = {}
    form_class = None
    success_url = None
    prefix = None

    def get_initial(self):
        """Return the initial data to use for forms on this view."""
        return self.initial.copy()

    def get_prefix(self):
        """Return the prefix to use for forms."""
        return self.prefix

    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)

    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)

先ほどのProcessFormViewのPOSTメソッドで呼び出されていたget_formから順に見ていきましょう!

get_formの中では、form_classをインスタンス化して返しています。

インスタンス化されるform_classは引数のform_classがNoneであればget_form_classメソッドで取得しています。 get_form_classメソッドではクラス変数のform_classを返しています。

動的にform_classを変更したければ、get_form_classget_formメソッド辺りをオーバーライドしてあげれば良いことがわかります。

form_classをインスタンス化する際に引数にget_form_kwargsの戻り値を与えています。 この中はどうなっているのでしょう?

get_initial,get_prefixメソッドを呼び出してそれぞれinitial,prefixのキーを持つ辞書にして返しています。
さらに、アクセスがPOSTメソッド又は、PUTメソッドの場合はdata、filesを追加しています。

initialはフォームの初期値、prefixはformのフィールドのidにつけるprefixです。
prefixは一つのページで複数のフォームを利用する際にそれぞれのフォームを区別するためにつけたりします。

data,filesは、送信されてきたデータです。
これらをform_classに渡して、バリデーションだったりの処理を行わせます。

get_initialの中身も見てみましょう。

とは言ってもやっていることはごく簡単で、クラス変数のinitialのコピーを作成して返しているだけです。
動的に、フォームの初期値を変更したい場合は、get_initialをオーバーライドしてあげれば良さそうです。

ちなみに、クラス変数のinitialやget_initialはキーがフィールド名、値がフォームの初期値の辞書です。

そうして、初期化されたフォームクラスのインスタンスに対してis_validメソッドを呼び出して、フォームの入力内容を検証します。

検証に成功した場合は、form_valid、失敗した場合には、form_invalidを返すようになっています。

form_validの中では、get_success_urlの戻り値を引数に渡して、リダイレクトするようにしています。
get_success_urlの中では、クラス変数のsuccess_urlを返しているだけです。
動的にリダイレクト先を変える場合にはget_success_urlをオーバーライドすれば良いですね。

form_invalidの中では、form_classを再度テンプレートに渡してページを表示させています。
検証に失敗した、formにはエラーの情報が含まれているのでこれをテンプレート側で表示してあげればユーザーに検証の失敗理由が表示できます。

まとめ

  • フォームに初期値を入れておくにはget_initialをオーバーライドする。
  • 動的にリダイレクト先を変えたい場合はget_success_urlをオーバーライドする。
  • 同じフォームを同一のページで利用する場合prefixを設定する。