聊聊Django基于类的视图(第三篇)

    最近重构了网站代码的视图一块,将之前的视图函数几乎全换成了类视图。自己也是编写边学习,又巩固了这方面的知识。Django的官网真的很全面,很细致,照着它边学边做就可以,如果实在弄不懂,就可以看Django的源码,源代码有你想要的一切。

    对于类视图,django内置的类提供了display,edit,dates等大类,但这几大类都是父类View的子类,或者说View是所有类视图的父类。View类包括最基本的视图方法,提供了不同请求方法的调度,代码解释:“Only implements dispatch-by-method and simple sanity checking.”。本文就先不说dates了,主要看下display和edit两大视图类。:

  • display用来显示object,主要包括:
  1. 对象列表,ListView;
  2. 单个对象,DetailView
  • edit用来编辑object,主要包括:
  1. 创建对象 CreateView;
  2. 更改对象 UpdateView;
  3. 删除对象 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'年龄必须为数字'
        }



 

--------EOF---------
微信分享/微信扫码阅读