处理表单一般有以下三个步骤:
- 在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']