處理表單一般有以下三個步驟:
- 在request.method爲GET時初始化表單並呈現在前端頁面。
- 對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):
#使用合法的數據發送郵件
pass
這裏的視圖用到FormView:
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):
#當合法數據被post時該方法被調用,並返回一個HttpResponse。
form.send_email()
return super().form_valid(form)
有兩點需要說明:
- FormView繼承了TemplateResponseMixin因此在FormVIew中可以使用template_name。
- form_valid()默認重定向到success_url。
二、使用ModelForm
如果類視圖能夠確定使用的模型類,那麼它將自動創建ModelForm,以下方式都能提供這個功能:
- 如果model屬性被提供,那麼類視圖將使用該模型類。
- 如果get_object()返回一個object,那麼該object依賴的模型類將會被使用。
- 如果queryset被提供,那麼queryset依賴的模型類將會被使用。
與模型窗體有關的視圖(CreateView、UpdateView、DeleteView)提供有form_valid()方法,它的功能是驗證數據並自動保存模型,如果你有另外的需求,請重寫它。
你甚至連success_url也不需要提供,前提是model類實現了 get_absolute_url() 方法。
另外,如果你不想讓Django幫你選擇 modelform 而是想自定義它,請使用form_class,但這時就又回退到普通表單處理的範疇了。
接下來看一個例子:
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})
我們在model類中實現了get_absolute_url()方法,它會根據獲得的model中的對象自動重定向。有了該方法,我們就不必在視圖中定義success_url了。
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')
這裏要注意,我們的邏輯是當創建一個對象,或者更新一個對象時,自動重定向到該對象的詳情界面,但刪除該對象的話,因爲對象被刪除,因此get_absolute_url()就不管用了,所以我們要自定義success_url。
這裏用reverse_lazy()實現,reverse_lazy()是reverse()的一個延遲版本,因爲刪除author後需要調用author-list,但這時可能url還沒有加載到。
這裏的fields屬性與ModelForm中的Meta類的fields在功能上是一樣的。
需要注意的是fields屬性與form_class不能一起使用,因爲一個表單不能既是普通表單又是ModelForm,如果你這麼做了,那麼Django將拋出 ImproperlyConfigured 錯誤。
最後,讓我們看一下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'),
]
【注意】
以上這些視圖均繼承自 SingleObjectTemplateResponseMixin。
雖然我們沒有指定模板,這時Django會使用默認值,其中,CreateView和UpdateView默認使用myapp/author_form.html,DeleteView默認使用myapp/author_confirm_delete.html,如果你希望自己指定模板,請設置 template_name 或者 template_name_suffix 參數。
三、Models與Request.user
現在我們希望CreateView創建的author自動映射向User模型中的user,且該user必須是當前登錄的用戶,以表名該author爲當前登錄的user所創建,這該怎麼做呢?
首先,修改我們的author模型。
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與User關聯,在ModelForm中,注意不要把該外鍵添加到fields中,我們需要在數據驗證時另外把created_by的值指向request.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會防止未登錄的用戶訪問這裏,如果你沒有添加該Mixin,你也可以在form_valid()中添加邏輯去處理。
四、Ajax示例
下面是一個如何在類視圖的表單處理中使用ajax的示例:
from django.http import JsonResponse
from django.views.generic.edit import CreateView
from myapp.models import Author
class AjaxableResponseMixin:
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):
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']