Two Scoops of Django 1.8 学习笔记四

8. Function- and Class-Based Views

  • Function-based views(FBVs) 函数视图
  • Class-based views(CBVs) 类视图

8.1 什么时候使用FBVs或者CBVs

每当你想实现一个view的时候,考虑下用哪种方法比较好,下面是各种情况下该使用FBVs还是CBVs流程图
FBVs_CBVs
我们更倾向于在大多数情况下使用CBVs来实现

8.2 将View的逻辑放在URLConfs之外

requests通过URLConfs被路由到views上,在一个模块中通常用urls.py表示。

URL设计哲学 https://docs.djangoproject.com/en/1.8/misc/design-philosophies/#url-design

要点很明确:

  • view模块应该包含view的逻辑
  • URL模块应该包含URL的逻辑

下面的代码,你可能在官方文档里见过:

Bad Example

from django.conf.urls import url
from django.views.generic import DetailView


from tastings.models import Tasting


urlpatterns = [
    url(r"^(?P<pk>\d+)/$",
        DetailView.as_view(
            model=Tasting,
            template_name="tastings/detail.html"),
        name="detail"),
    url(r"^(?P<pk>\d+)/results/$",
        DetailView.as_view(
            model=Tasting,
            template_name="tastings/results.html"),
        name="results"),
]

瞟一眼代码可能感觉是对的,但是它违反了Django的设计哲学:

  • 它用tight coupling(紧耦合)取代了Loose coupling(松耦合),意味着不能重用这个view
  • 违反了DRY原则,在CBVs之间使用了同一个或者相似的参数
  • 破坏了可扩展性,CBVs的优势是类的继承,可他在用反面模式
  • 太多其他问题:当你加上身份认证的时候会发生什么?授权怎么办?你准备用两个或者更多的装饰器封装每个URLConfs视图吗?
    把你的view代码放在URLConfs中导致你的URLConfs混乱不利于维护

8.3 坚持松耦合的URLConfs

为了避免上面提到过的问题,我们这样创建URLConfs,先写个views

# tastings/views.py
from django.views.generic import ListView, DetailView, UpdateView
from django.core.urlresolvers import reverse

from .models import Tasting

class TasteListView(ListView):
    model = Tasting

class TasteDetailView(DetailView):
    model = Tasting

class TasteResultsView(TasteDetailView):
    template_name = "tastings/results.html"

class TasteUpdateView(UpdateView):
    model = Tasting

    def get_success_url(self):
        return reverse("tastings:detail",
            kwargs={"pk": self.object.pk})

再写个urls

# tastings/urls.py
from django.conf.urls import url

from . import views

urlpatterns = [
    url(
        regex=r"^$",
        view=views.TasteListView.as_view(),
        name="list"
    ),
    url(
        regex=r"^(?P<pk>\d+)/$",
        view=views.TasteDetailView.as_view(),
        name="detail"
    ),
    url(
        regex=r"^(?P<pk>\d+)/results/$",
        view=views.TasteResultsView.as_view(),
        name="results"
    ),
    url(
        regex=r"^(?P<pk>\d+)/update/$",
        view=views.TasteUpdateView.as_view(),
        name="update"
    )
]

为什么要这样写呢:

  • DRY原则:没有参数和属性的重复
  • 松耦合:我们从URLConfs中移除了model和template name,因为URLConfs就是URLConfs,view就是view。
    我们应该尽可能的从一个或者更多的URLConfs调用views,这个例子就做到了
  • URLConfs should do one thing and do it well:这个例子中我们的URLConfs只专注于roating
  • views 能从Class-based中受益:views可以继承自其他的类,这样,加个身份认证,授权,新的评论格式或者其他的业务需求就变得更容易了
  • 无限的扩展性:在views中,我们能实现自定义逻辑

8.4 使用URL 命名空间

URL 命名空间给app-level提供了一个标识符,命名空间在表面上看起来没什么软用,但是一旦开发者开始用的时候就会抱怨为什么不早点使用。
我们总结了使用命名空间的规则:

  • 我们像这样写 tastings:detail (冒号后面不要写空格!不要写空格!不要写空格!)

在root URLConfs上们加上:

# urls.py at root of project
urlpatterns += [
    url(r'^tastings/', include('tastings.urls', namespace='tastings')),
]

再看下view的代码片段:

# tastings/views.py snippet
class TasteUpdateView(UpdateView):
    model = Tasting

    def get_success_url(self):
        return reverse("tastings:detail",
            kwargs={"pk": self.object.pk})

在HTML这样使用:

{% raw %}

    {% extends "base.html" %}

    {% block title %}Tastings{% endblock title %}

    {% block content %}
    {% endraw %}
<ul>
{% raw %}
      {% for taste in tastings %}
    {% endraw %}
    <li>
      <a href="{% raw %}{% url "tastings:detail" taste.pk %}">{{ taste.title }}{% endraw %}</a>
      <small>
        <a {% raw %}href="{% url "tastings:update" taste.pk %}">update{% endraw %}</a>
      </small>
    </li>
{% raw %}
      {% endfor %}
    {% endraw %}
</ul>
{% raw %}
    {% endblock content %}
    {% endraw %}

8.4.1创建更短,更显而易见,DRY的URL名

8.4.2通过第三方库增加互通性

如果已经存在一个contact的app,但是我们还要加一个,我们可以这样:

# urls.py at root of project
urlpatterns += [
    url(r'^contact/', include('contactmonger.urls',
        namespace='contactmonger')),
    url(r'^report-problem/', include('contactapp.urls',
        namespace='contactapp')),
]

在templates让它生效

{% raw %}

    {% extends "base.html" %}
    {% block title %}Contact{% endblock title %}
    {% block content %}
    {% endraw %}
    <p>

  <a href="{% raw %}{% url "contactmonger:create" %}{% endraw %}">Contact Us</a>
    </p>
    <p>

  <a href="{% raw %}{% url "contactapp:report" %}{% endraw %}">Report a Problem</a>
    </p>
{% raw %}
    {% endblock content %}

    {% endraw %}

8.5 在URLConfs中引用views的时候不要用字符串

在Django1.8之前的官方文档中,在URLConfs引用views的时候,会把views作为字符串:

BAD Example
# DON'T DO THIS!
# polls/urls.py
from django.conf.urls import patterns, url


urlpatterns = patterns('',
    # Defining the view as a string
    url(r'^$', 'polls.views.index', name='index'),
)

这种方法有两个问题:

  • Django 神奇的添加了view函数/类,这个魔法特性的问题是当view有个错误时,增加了调试的困难性
  • 这个方法需要把空字符串作为patterns的前缀,太麻烦

来看看patterns的源码:

def patterns(prefix, *args):
    warnings.warn(
        'django.conf.urls.patterns() is deprecated and will be removed in '
        'Django 1.10. Update your urlpatterns to be a list of '
        'django.conf.urls.url() instances instead.',
        RemovedInDjango110Warning, stacklevel=2
    )
    pattern_list = []
    for t in args:
        if isinstance(t, (list, tuple)):
            t = url(prefix=prefix, *t)
        elif isinstance(t, RegexURLPattern):
            t.add_prefix(prefix)
        pattern_list.append(t)
    return pattern_list

它自己都说了在1.10版本之后就会移除,可官方文档还在这样写,sibusisha….

以下是定义views的正确方法:

# polls/urls.py
from django.conf.urls import url

from . import views

urlpatterns = [
    # Defining the views explicitly
    url(r'^$', views.index, name='index'),
]

参考: https://docs.djangoproject.com/en/1.8/releases/1.8/#django-conf-urls-patterns

8.6 将业务逻辑放在views之外

过去,我们会把复杂的业务逻辑放在views里面,不幸的是,当需要在我们的views中生成PDF,加一个 REST API,或者要服务于其他版式时,它实现起来会变得很困难。
这就是我们更倾向使用model methods, manager methods, 或者常规实用的辅助功能的原因。当把业务逻辑放在更容易复用的组件中,再从views内调用,
通过扩展组件可以让项目更容易做更多的事情。

8.7 Django Views Are Functions

# Django FBV as a function
HttpResponse = view(HttpRequest)

# Deciphered into basic math (remember functions from algebra?)
y = f(x)

# ... and then translated into a CBV example
HttpResponse = View.as_view()(HttpRequest)

这个函数将一个HTTP 请求对象转换成了HTTP 响应对象。你可以理解成数学中的函数。
CBV通过as_view()也可以作为函数使用。

最简单的views

# simplest_views.py
from django.http import HttpResponse
from django.views.generic import View

# The simplest FBV
def simplest_view(request):
    # Business logic goes here
    return HttpResponse("FBV")

# The simplest CBV
class SimplestView(View):
    def get(self, request, *args, **kwargs):
        # Business logic goes here
        return HttpResponse("CBV")

为什么需要了解?

  • 有时我们需要一次性的views来做一件小事
  • 理解了最简单的views意味着我们能更好的理解这样做的目的
  • 说明 FBVs不用指定HTTP方法(GET,POST,DELETE等等),而CBVs需要指定特定的HTTP方法

8.8 不要使用locals()作为Views Context

从任何调用中返回 locals() 是 anti-pattern(反面模式)。虽然它看上去方便快捷,事实上它太特么耗时了。
我们来举一个anti-pattern的例子:

BAD Example

def ice_cream_store_display(request, store_id):
    store = get_object_or_404(Store, id=store_id)
    date = timezone.now()
    return render(request, 'melted_ice_cream_report.html', locals())

表面看起来一切OK,然而,因为我们把一个本应该显式的设计,捣鼓成了隐式的anti-pattern,让这个简单的view有了维护的烦恼。
特别强调的是我们根本不知道它会返回什么结果。我们改变任一个变量,返回结果都不会立刻呈现。
这就是为什么我们强烈的提倡在views中使用明确的上下文:

def ice_cream_store_display(request, store_id):
    return render(request, 'melted_ice_cream_report.html', dict{
        'store': get_object_or_404(Store, id=store_id),
        'now': timezone.now()
    })
如果我的文章对你有很大帮助 那么不妨?
0%