クラスベースのビューでフォームを扱う

フォームの処理には、一般に3つの場合分けが存在します。

  • 最初の GET リクエスト (空白またはデフォルト値が埋め込まれたフォームを返す)
  • 無効なデータの POST リクエスト (よくあるパターンは、エラー表示を追加したフォームを再表示する)
  • 有効なデータの POST リクエスト (データを処理し、普通はリダイレクトを行う)

これらの処理を自分で実装しようとすると、多くの場合、多数の繰り返しの定型コードを書くことになってしまいます (Using a form in a view を参照)。これを避けるために、Django はフォームを処理するための一般的なクラスビューを用意しています。

基本的なフォーム

以下のような単純なコンタクトフォームがあったとします。

forms.py
from django import forms

class ContactForm(forms.Form):
    name = forms.CharField()
    message = forms.CharField(widget=forms.Textarea)

    def send_email(self):
        # send email using the self.cleaned_data dictionary
        pass

このとき、ビューは FormView を使用することで構築することができます。

views.py
from myapp.forms import ContactForm
from django.views.generic.edit import FormView

class ContactView(FormView):
    template_name = 'contact.html'
    form_class = ContactForm
    success_url = '/thanks/'

    def form_valid(self, form):
        # This method is called when valid form data has been POSTed.
        # It should return an HttpResponse.
        form.send_email()
        return super().form_valid(form)

メモ:

モデルフォーム

ジェネリックビューが輝きを見せるのは、モデルとともに使用した時です。ジェネリックビューは ModelForm を自動的に生成するので、どのモデルクラスを使うのかを選択することができます。

  • model 属性が与えられた場合には、そのモデルクラスが使用されます。
  • get_object() がオブジェクトを帰す場合には、そのオブジェクトのクラスが使用されます。
  • queryset が与えられた場合には、そのクエリセットに対するモデルが使用されます。

Model form views provide a form_valid() implementation that saves the model automatically. You can override this if you have any special requirements; see below for examples.

You don't even need to provide a success_url for CreateView or UpdateView - they will use get_absolute_url() on the model object if available.

カスタムした ModelForm を使用したい場合 (例えば、追加的なバリデーションを加えたい場合)、シンプルに form_class をビューでセットしてください。

注釈

カスタムのフォームクラスを指定した場合、form_classModelForm だったとしても、モデルを指定する必要があります。

最初に、Author クラスに get_absolute_url() を追加する必要があります:

models.py
from django.db import models
from django.urls import reverse

class Author(models.Model):
    name = models.CharField(max_length=200)

    def get_absolute_url(self):
        return reverse('author-detail', kwargs={'pk': self.pk})

そうしたら、CreateView およびその仲間たちを使って実際に動作させることができます。ここでは一般的なクラスベースのビューのみを設定している点が重要です; 自分自身でロジックを書く必要はありません:

views.py
from django.urls import reverse_lazy
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from myapp.models import Author

class AuthorCreate(CreateView):
    model = Author
    fields = ['name']

class AuthorUpdate(UpdateView):
    model = Author
    fields = ['name']

class AuthorDelete(DeleteView):
    model = Author
    success_url = reverse_lazy('author-list')

注釈

ファイルをインポートしたときには URL は読み込まれないので、単なる reverse() ではなく reverse_lazy() を使用する必要があります。

The fields attribute works the same way as the fields attribute on the inner Meta class on ModelForm. Unless you define the form class in another way, the attribute is required and the view will raise an ImproperlyConfigured exception if it's not.

If you specify both the fields and form_class attributes, an ImproperlyConfigured exception will be raised.

Finally, we hook these new views into the URLconf:

urls.py
from django.urls import path
from myapp.views import AuthorCreate, AuthorDelete, AuthorUpdate

urlpatterns = [
    # ...
    path('author/add/', AuthorCreate.as_view(), name='author-add'),
    path('author/<int:pk>/', AuthorUpdate.as_view(), name='author-update'),
    path('author/<int:pk>/delete/', AuthorDelete.as_view(), name='author-delete'),
]

注釈

These views inherit SingleObjectTemplateResponseMixin which uses template_name_suffix to construct the template_name based on the model.

In this example:

If you wish to have separate templates for CreateView and UpdateView, you can set either template_name or template_name_suffix on your view class.

Models and request.user

To track the user that created an object using a CreateView, you can use a custom ModelForm to do this. First, add the foreign key relation to the model:

models.py
from django.contrib.auth.models import User
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=200)
    created_by = models.ForeignKey(User, on_delete=models.CASCADE)

    # ...

In the view, ensure that you don't include created_by in the list of fields to edit, and override form_valid() to add the user:

views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from myapp.models import Author

class AuthorCreate(LoginRequiredMixin, CreateView):
    model = Author
    fields = ['name']

    def form_valid(self, form):
        form.instance.created_by = self.request.user
        return super().form_valid(form)

LoginRequiredMixin prevents users who aren't logged in from accessing the form. If you omit that, you'll need to handle unauthorized users in form_valid().

AJAX の例

以下に挙げるのは、「普通の」フォームが POST するのと同様に AJAX リクエストで動作するフォームの簡単な実装例です。

from django.http import JsonResponse
from django.views.generic.edit import CreateView
from myapp.models import Author

class AjaxableResponseMixin:
    """
    Mixin to add AJAX support to a form.
    Must be used with an object-based FormView (e.g. CreateView)
    """
    def form_invalid(self, form):
        response = super().form_invalid(form)
        if self.request.is_ajax():
            return JsonResponse(form.errors, status=400)
        else:
            return response

    def form_valid(self, form):
        # We make sure to call the parent's form_valid() method because
        # it might do some processing (in the case of CreateView, it will
        # call form.save() for example).
        response = super().form_valid(form)
        if self.request.is_ajax():
            data = {
                'pk': self.object.pk,
            }
            return JsonResponse(data)
        else:
            return response

class AuthorCreate(AjaxableResponseMixin, CreateView):
    model = Author
    fields = ['name']