使用基於類別的視圖處理表單

表單處理通常有 3 個路徑

  • 初始 GET (空白或預先填入的表單)

  • POST 帶有無效資料 (通常會重新顯示帶有錯誤的表單)

  • POST 帶有有效資料 (處理資料,通常會重新導向)

自己實作這個通常會導致大量重複的樣板程式碼 (請參閱在視圖中使用表單)。為了避免這種情況,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 ContactFormView(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,將會使用該查詢集的模型。

模型表單視圖提供自動儲存模型的 form_valid() 實作。如果您有任何特殊需求,可以覆寫它;請參閱下面的範例。

您甚至不需要為 CreateViewUpdateView 提供 success_url - 如果模型物件上可用,它們將會使用 get_absolute_url()

如果您想使用自訂的 ModelForm (例如新增額外的驗證),請在您的視圖上設定 form_class

註解

當指定自訂表單類別時,您仍然必須指定模型,即使 form_class 可能是一個 ModelForm

首先,我們需要在 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 AuthorCreateView(CreateView):
    model = Author
    fields = ["name"]


class AuthorUpdateView(UpdateView):
    model = Author
    fields = ["name"]


class AuthorDeleteView(DeleteView):
    model = Author
    success_url = reverse_lazy("author-list")

註解

我們必須使用 reverse_lazy() 而不是 reverse(),因為在匯入檔案時尚未載入 URL。

fields 屬性的運作方式與 ModelForm 上內部 Meta 類別的 fields 屬性相同。除非您以其他方式定義表單類別,否則該屬性是必要的,如果沒有該屬性,視圖將會引發 ImproperlyConfigured 例外。

如果您同時指定 fieldsform_class 屬性,則會引發 ImproperlyConfigured 例外。

最後,我們將這些新的視圖掛接到 URLconf 中

urls.py
from django.urls import path
from myapp.views import AuthorCreateView, AuthorDeleteView, AuthorUpdateView

urlpatterns = [
    # ...
    path("author/add/", AuthorCreateView.as_view(), name="author-add"),
    path("author/<int:pk>/", AuthorUpdateView.as_view(), name="author-update"),
    path("author/<int:pk>/delete/", AuthorDeleteView.as_view(), name="author-delete"),
]

註解

這些視圖繼承了 SingleObjectTemplateResponseMixin,它使用 template_name_suffix 來根據模型建構 template_name

在這個範例中

如果您希望 CreateViewUpdateView 有個別的範本,您可以在您的視圖類別上設定 template_nametemplate_name_suffix

模型和 request.user

若要追蹤使用 CreateView 建立物件的使用者,您可以使用自訂的 ModelForm 來達成。首先,將外鍵關聯新增至模型中

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)

    # ...

在視圖中,請確保您未將 created_by 包含在要編輯的欄位列表中,並覆寫 form_valid() 以新增使用者

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


class AuthorCreateView(LoginRequiredMixin, CreateView):
    model = Author
    fields = ["name"]

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

LoginRequiredMixin 可防止未登入的使用者存取表單。如果您省略了該項,則需要在 form_valid() 中處理未經授權的使用者。

內容協商範例

這裡有一個範例,展示如何實作一個同時適用於基於 API 的工作流程和「正常」表單 POST 的表單

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


class JsonableResponseMixin:
    """
    Mixin to add JSON 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.accepts("text/html"):
            return response
        else:
            return JsonResponse(form.errors, status=400)

    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.accepts("text/html"):
            return response
        else:
            data = {
                "pk": self.object.pk,
            }
            return JsonResponse(data)


class AuthorCreateView(JsonableResponseMixin, CreateView):
    model = Author
    fields = ["name"]
返回頂部