使用基於類別的視圖處理表單¶
表單處理通常有 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)
註解
FormView 繼承了
TemplateResponseMixin
,所以這裡可以使用template_name
。form_valid()
的預設實作只是重新導向到success_url
。
模型表單¶
當使用模型時,通用視圖真正發光。只要可以找出要使用的模型類別,這些通用視圖會自動建立 ModelForm
。
如果給定了
model
屬性,將會使用該模型類別。如果
get_object()
傳回一個物件,將會使用該物件的類別。如果給定了
queryset
,將會使用該查詢集的模型。
模型表單視圖提供自動儲存模型的 form_valid()
實作。如果您有任何特殊需求,可以覆寫它;請參閱下面的範例。
您甚至不需要為 CreateView
或 UpdateView
提供 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
例外。
如果您同時指定 fields
和 form_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
。
在這個範例中
CreateView
和UpdateView
使用myapp/author_form.html
DeleteView
使用myapp/author_confirm_delete.html
如果您希望 CreateView
和 UpdateView
有個別的範本,您可以在您的視圖類別上設定 template_name
或 template_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"]