聊聊Django基于类的视图(第三篇)
最近重构了网站代码的视图一块,将之前的视图函数几乎全换成了类视图。自己也是编写边学习,又巩固了这方面的知识。Django的官网真的很全面,很细致,照着它边学边做就可以,如果实在弄不懂,就可以看Django的源码,源代码有你想要的一切。
对于类视图,django内置的类提供了display,edit,dates等大类,但这几大类都是父类View的子类,或者说View是所有类视图的父类。View类包括最基本的视图方法,提供了不同请求方法的调度,代码解释:“Only implements dispatch-by-method and simple sanity checking.”。本文就先不说dates了,主要看下display和edit两大视图类。:
- display用来显示object,主要包括:
- 对象列表,ListView;
- 单个对象,DetailView
- edit用来编辑object,主要包括:
- 创建对象 CreateView;
- 更改对象 UpdateView;
- 删除对象 DeleteView;
视图类都是多重继承,display一般都继承自基于类的Minxin,因为其可提供更细致的功能,避免了代码重复,如ListView继承自 MultipleObjectMixin,它用来获取QuerySet,即查询集;再比如DetailView,它继承自SingleObjectTemplateResponseMixin,主要获得单个的object对象。
edit的几个类视图都是FormView的,FormView与他们最大的不同是在发生请求的时候,要自动获得form,即多了get_form等方法,可以理解为FormView是表单对对象的包装。我们看下FormView的源码:
class FormMixin(six.with_metaclass(FormMixinBase, ContextMixin)):
"""
A mixin that provides a way to show and handle a form in a request.
"""
form_class = None
def get_form_class(self):
"""
Returns the form class to use in this view
"""
return self.form_class
def get_form(self, form_class=None):
"""
Returns an instance of the form to be used in this view.
"""
if form_class is None:
form_class = self.get_form_class()
return form_class(**self.get_form_kwargs())
def get_form_kwargs(self):
"""
Returns the keyword arguments for instantiating the form.
"""
kwargs = {
'initial': self.get_initial(),
'prefix': self.get_prefix(),
}
if self.request.method in ('POST', 'PUT'):
kwargs.update({
'data': self.request.POST,
'files': self.request.FILES,
})
return kwargs
def form_valid(self, form):
"""
If the form is valid, redirect to the supplied URL.
"""
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form):
"""
If the form is invalid, re-render the context data with the
data-filled form and errors.
"""
return self.render_to_response(self.get_context_data(form=form))
def get_context_data(self, **kwargs):
"""
Insert the form into the context dict.
"""
if 'form' not in kwargs:
kwargs['form'] = self.get_form()
return super(FormMixin, self).get_context_data(**kwargs)
对于CreateView或者UpdateView在发生GET或者POST请求的时候都会get_form表单,它们会在发生POST请求的时候,自动添加request.POST的数据。看源代码:
class ProcessFormView(View):
"""
A mixin that renders a form on GET and processes it on POST.
"""
def get(self, request, *args, **kwargs):
"""
Handles GET requests and instantiates a blank version of the form.
"""
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
再创建FormView类的子类时,一定要注意你的Form类是否还有额外的参数,如果有就必须要自己添加,即要重构get_form方法。比如我的修改密码的表单,就首先要传入user。但父类的get_form方法默认是不添加的,这时候就会报错。可能说到这,还有些晕,我拿代码说话。
我的目的是对于登录用户药修改密码的需求。我写的类视图:
class ChangePasswdView(FormView):
template_name = 'account/changepassword.html'
success_url = '/account/login/'
form_class = UserChangePasswdForm
#the UserChangePasswdForm must add user parameter,so need reconstruct get_form
def get_form(self, form_class=None):
if form_class is None:
form_class = self.get_form_class()
return form_class(self.request.user,**self.get_form_kwargs())
def form_valid(self, form):
form.save()
auth.logout(self.request)
return super(ChangePasswdView,self).form_valid(form)
看之前我的介绍,GET和POST请求都会调用get_form,父类的get_form方法逻辑是这样的:
def get_form(self, form_class=None):
"""
Returns an instance of the form to be used in this view.
"""
if form_class is None:
form_class = self.get_form_class()
return form_class(**self.get_form_kwargs())
但我们的UserChangePasswdForm必须要传入user参数,代码:
class SetPasswordForm(forms.Form):
def __init__(self, user, *args, **kwargs):
self.user = user
super(SetPasswordForm, self).__init__(*args, **kwargs)
看到了吗?如果你只传入kwargs,是会报错滴!因此我重构了get_form方法。
其实看到上面我的代码就应该明白了,如果你用了FormView及子类,那你会省下很多工作。对于GET请求,如果你不想获得除了form表单之类的上下文,你完全可以不写。对于POST请求,你只需要重构form_valid和form_invalid(有时,invalid都不需要重构,只要你在每个表单定义了error消息,那么表单数据无效时,会自动在界面提示)。来看一个完整例子:
class ArticleMinxin(BaseMixin):
"""
the Minxin provides categories,they are useful for some palces,such as ,add modify,article.
"""
def get_context_data(self,*args,**kwargs):
context = super(ArticleMinxin,self).get_context_data(*args,**kwargs)
context['categories'] = Category.objects.all()
return context
class CreateArticleView(ArticleMinxin,CreateView):
form_class = ArticleForm
template_name = 'dashboard/add_article.html'
success_url = '/dashboard/'
model = Article
def form_valid(self, form):
trip_info = form.form_save(self.request.user)
if trip_info:
article_url, title, img, abstract, trip_date = trip_info
add_trip.delay(article_url, title, img, abstract, trip_date)
return HttpResponseRedirect(self.success_url)
def form_invalid(self, form):
"""
add your logical here for invalid form.
"""
return super(CreateArticleView,self).form_invalid(form)
其实,我最后的form_invalid是废话。你可以在中间添加一些逻辑。
上面简单地介绍了类视图,视图和表单是密不可分的,哈哈。所以还要把表单掌握好,其实我都没怎么看官方文档,主要是看了代码。form也可分为基本Form以及ModelForm,ModelForm增加了一些特殊方法,比如通过save保存,这就有点像设计模式中的代理模式,form代理Model实现了一些特殊方法。
就粘两段我的代码,仅供参考:
class UserLoginForm(Form):
error_messages = {
'password_incorrect':u'password incorrect'
}
username = CharField(max_length=30,
error_messages={
'required':u"用户名未填写",
'invalid':u"用户名不符合标准"},
help_text=u'用户名必须为数字,字母或下划线等')
password = CharField(
widget=PasswordInput,
error_messages={
'required': u"密码未填写"
}
)
class UserProfileForm(ModelForm):
class Meta:
model = UserProfile
#fields = ['avator','age','school','gender','hobby',
# 'motto','self_introduction','birthday','user']
fields = '__all__'
error_messages = {
'age': {
'required': u"年龄未填写",
'invalid': u"必须是整数"},
}
help_text = {
'age':u'年龄必须为数字'
}
微信分享/微信扫码阅读