Django写的多了,有些问题才逐渐认识到。

比如有一个view比较复杂,调用了很多其他的函数。想要把这些函数封装起来,怎么办?

当然,可以用注释#------view------这样将函数隔离开,这种方法太low了,简直是在骗自己,连封装都算不上。
Python是一个面向对象的编程语言,如果只用函数来开发,有很多面向对象的优点就错失了(继承、封装、多态)。
所以Django在后来加入了Class-Based-View。可以让我们用类写View。
这样做的优点主要下面两种:
(1)提高了代码的复用性,可以使用面向对象的技术,比如Mixin(多继承);
(2)可以用不同的函数针对不同的HTTP方法处理,而不是通过很多if判断,提高代码可读性;

 

要理解django的class-based-view(以下简称cbv),首先要明白django引入cbv的目的是什么。在django1.3之前,generic view也就是所谓的通用视图,使用的是function-based-view(fbv),亦即基于函数的视图。有人认为fbv比cbv更pythonic,但是,python的一大重要的特性就是面向对象。而cbv更能体现python的面向对象。cbv是通过class的方式来实现视图方法的。class相对于function,更能利用多态的特定,因此更容易从宏观层面上将项目内的比较通用的功能抽象出来。 cbv的实现原理通过看django的源码就很容易明白,大体就是由url路由到这个cbv之后,通过cbv内部的dispatch方法进行分发,将get请求分发给cbv.get方法处理,将post请求分发给cbv.post方法处理,其他方法类似。并且,cbv里引入了mixin的概念。Mixin就是写好了的一些基础类,然后通过不同的Mixin组合成为最终想要的类。
所以,理解cbv的基础是,理解Mixin。Django中使用Mixin来重用代码,一个View Class可以继承多个Mixin,但是只能继承一个View(包括View的子类),推荐把View写在最右边,多个Mixin写在左边。Mixin也是比较复杂的技术 。

 

 

如何正确使用 CBVs (Class-based views)

 

1. CBVs的使用原则

  • 代码越少越好
  • 永远不要重复代码
  • View应当只包含呈现逻辑, 不应包括业务逻辑
  • 保持view逻辑清晰简单
  • 不要将CBVs用作403, 404, 500的错误处理程序
  • 保持mixin简单明了

2. 如何使用mixin

在编程中mixin是指为继承它的class提供额外的功能, 但它自身却不能单独使用的类. 在具有多继承能力的编程语言中, mixin可以为类增加额外功能或方法. 在Django中, 我们可以使用mixin为CBVs提供更多的扩展性, 当然在类继承过程中, 我们推荐以下原则:

  • Django自身提供的View永远在最右边
  • mixin依次在以上view的左边
  • mixin永远继承自Python的object类型

在这里顺便推荐一个很好的django库: django-braces. 该库中提供众多django的mixin, 可以方便我们日常使用.

以下是一个简单地例子, TemplateView是django自身提供的基本View, 因此在最右边; FreshFruitMixin则在TemplateView左边; FreshFruitmixin继承自object:

    from django.views.generic import TemplateView

    class FreshFruitMixin(object):

        def get_context_data(self, **kwargs):
            context = super(FreshFruitMixin, self).get_context_data(**kwargs)
            context["has_fresh_fruit"] = True
            return context

    class FruitFlavorView(FreshFruitMixin, TemplateView):
        template_name = "fruit_flavor.html"

3. 如何使用Django自身的CBV

CBVs在功能上的可扩展性, 牺牲的是简单性, 一个CBV最多的时候拥有8个import关系. (如果希望进一步了解这些继承关系, 可以使用Classy Class-Based Views进行查看.) 所以要弄懂那个View最适合当下的场景对于开发人员也是一个挑战. 为了减少CBVs的使用难度, 我们将这些View和基本的用法列在下表中, 为了显示方便, 名字前的django.views.generic前缀皆省去:

名字目的例子
View 基本View, 可以在任何时候使用 见后面详细介绍
RedirectView 重新定向到其他URL 将访问"/log-in/"的用户重新定向到"/login/"
TemplateView 显示Django HTML template 一般网站中使用模板显示的页
ListView 显示对象列表 文章列表页
DetailView 显示对象详情 文章详细页
FormView 提交From 网站联系我们或emai订阅form
CreateView 创建对象 创建新文章页
UpdateView 更新对象 修改文章页
DeleteView 删除对象 删除文章页
Generic date views 显示一段时间内的对象 按时间归类的博客

4. CBVs的使用技巧

a. 限定访问权限

在django tutorial中介绍了如何一起使用django.contrib.auth.decorators.login_required和CBV, 这是一个典型的错误例子.

还好, 我们有django-braces. 在django-braces中已经提供了一个LoginRequiredMixin:

    # myapp/views.py
    from django.views.generic import DetailView

    from braces.views import LoginRequiredMixin

    from .models import Article

    class ArticleDetailView(LoginRequiredMixin, DetailView):
        model = Article

b. 在form提交成功后执行代码

当需要在form提交成功后执行自定义的代码时, 可以使用form_valid()方法, form_valid()方法返回的是django.http.HttpResponseRedirect:

    # myapp/views.py
    from django.views.generic import CreateView

    from braces.views import LoginRequiredMixin

    from .models import Article

    class ArticleCreateView(LoginRequiredMixin, CreateView):
        model = Article
        fields = ('title', 'slug', 'content')

        def form_valid(self, form):
            # 自定义的代码逻辑写在这里
            return super(ArticleCreateView, self).form_valid(form)

c. 在form提交不成功后执行代码

当需要在form提交不成功后执行自定义的代码时, 可以使用form_invalid()方法, form_invalid()方法返回的也是django.http.HttpResponseRedirect:

    # myapp/views.py
    from django.views.generic import CreateView

    from braces.views import LoginRequiredMixin

    from .models import Article

    class ArticleCreateView(LoginRequiredMixin, CreateView):
        model = Article

        def form_invalid(self, form):
            # 自定义的代码逻辑写在这里
            return super(ArticleCreateView, self).form_invalid(form)

5. CBV和form如何结合使用

下面我们介绍一下常见的django form和CBV结合使用的模式, 首先我们定义一个Article model方便举例:

    # myapp/models.py
    from django.db import models
    from django.core.urlresolvers import reverse

    STATUS = {
        (0, 'zero'),
        (1, 'one'),
    }

    class Article(models.Model):
        title = model.CharField(max_length=255)
        slug = model.SlugField()
        review_num = models.IntegerField(default=0, choices=STATUS)

        def get_absolute_url(self):
            return reverse("article_detail", kwargs={"slug": self.slug})

a. Views和ModelForm

下面的例子中, 我们利用django.contrib.messages和CBVs构建一套创建, 更新和显示一篇article的view, 包括:

  • ArticleCreateView: 用于创建新article
  • ArticleUpdateView: 用于更新article
  • ArticleDetailView: 用于确认创建或更新后的article
    # myapp/views.py
    from django.contrib import messages
    from django.views.generic import CreateView, UpdateView, DetailView

    from braces.views import LoginRequiredMixin

    from .models import Article

    class ArticleActionMixin(object):
        @property
        def success_msg(self):
            return NotImplemented

        def form_valid(self, form):
            messages.info(self.request, self.success_msg)
            return super(ArticleActionMixin, self).form_valid(form)

    class ArticleCreateView(LoginRequiredMixin, ArticleActionMixin, CreateView):
        model = Article
        fields = ('title', 'slug', 'review_num')
        success_msg = "Article Created!"

    class ArticleUpdateView(LoginRequiredMixin, ArticleActionMixin, UpdateView):
        model = Article
        fields = ('title', 'slug', 'review_num')
        success_msg = "Article Updated!"

    class ArticleDetailView(DetailView):
        model = Article

接下来是template

    {# templates/myapp/article_detail.html #}
    {% if messages %}
    <ul class="messages">
        {% for message in messages %}
        <li>{ message } </li>
    </ul>
    {% endif %}

b. Views和Form

下面我们以搜索article功能为例子, 介绍一下CBV和form的常见使用样式, 在article列表页中点击搜索按钮, 显示搜友符合条件的article列表:

    # myapp/views.py
    from django.views.generic import ListView
    
    from .models import Article
    
    class ArticleListView(ListView):
        model = Article
        
        def get_queryset(self):
            queryset = super(ArticleListView, self).get_queryset()
            
            q = self.request.GET.get('q')
            
            if q:
                return queryset.filter(title__icontains=q)
                
            return queryset

然后可以使用include以下tenplate呈现搜索form:

    {# templates/myapp/_article_search.html #}
    <form action="{% url "article_list" %} method="GET"">
        <input type="text" name="q"></>
        <button type="submit">搜索</>
    </form>

6. 单独使用View

只用django.views.generic.View, 而不用FBV来构建所有django项目中的view也是可行的, 这也没有你所想象的那么复杂. 使用View的好处是, 我们不需要写许多内套式的if语句, 我们可以直接覆盖使用View的get(), post()等方法:

    from django.shortcuts import get_object_or_404, render, redirect
    from django.views.generic import View
    
    from braces.views import LoginRequiredMixin
    
    from .forms import ArticleForm
    from .models import Article
    
    class ArticleView(LoginRequiredMixin, View):
        
        def get(self, request, *args, **kwargs):
            article = get_object_or_404(Article, pl=kwargs['slug'])
            return render(request, 
                "myapp/article_detail.html",
                {"article": article}
            )
            
        def post(sele, request, *args, **kwargs):
            article = get_object_or_404(Article, pl=kwargs['slug'])
            form = ArticleForm(request.POST)
            if form.is_valid():
                form.save()
            return redirect("myapp:article", article.slug)