Django 1.6 最佳实践: 如何正确使用 FBVs (Function-based views)

在我们的博客中, 记录了我们在开发过程中所使用的技术和遇到的问题, 希望作为其他开发和设计者的一个学习交流平台.

Django 1.6 最佳实践: 如何正确使用 FBVs (Function-based views)


在Django发展的前期阶段, function-based views被开发人员广为使用. 即使在class-based views出现之后, 并且django的作者也推荐使用CBVs (Class-based views), 还是有大量的django新手或经验丰富的开发人员因为FBV说的简洁, 而还在使用FBVs. 接下来我们主要讨论一下使用FBVs时的最佳实践.

1. FBVs的优势

FBVs相对于CBVs, 缺少代码重复领用性, 因为FBVs无法像CBVs一样继承class. 但同时FBVs比CBVs具有更易读性. 以下是一些编写FBVs的原则:

  • 代码越少越好
  • 永远不要重复代码
  • View应当只包含呈现逻辑, 不应包括业务逻辑
  • 保持view逻辑清晰简单
  • 可以用作403, 404, 500的错误处理程序
  • 应尽量避免内套的"if"逻辑

2. 多使用HttpRequest对象

有时我们想将在view中重复使用一段代码, 又不想把它写入到middleware或context processor中. 我们应当将这些可重复使用的工具程序归并到utils.py中, 方便今后在整个项目中再使用.

对于这种小工具程序, 通常我们的逻辑是从django.http.HttpRequest中提取一些属性, 然后稍加处理, 完成操作. 我们发现, 如果将HttpRequest作为主要参数传入和传出这些工具程序中, 可以大大简化操作和提高可用性:

    # myapp/utils.py

    from django.core.exceptions import PermissionDenied

    def check_rights(request):
        if request.user.can_do_this or request.user.is_staff:
            return request

        # 返回 Http 403 错误
        raise PermissionDenied

check_rights检查用户是否拥有某项权限, 如果没有则返回403错误. 你会发现, 该程序传回的是HttpRequest对象, 而不是其他特定的值或None. 这样做的好处是, Python是动态类型语言, 我们可以在必要时为HttpRequest添加属性:

    # myapp/utils.py

    from django.core.exceptions import PermissionDenied

    def check_rights(request):
        if request.user.can_do_this or request.user.is_staff:
            # 在这里添加can_do_this属性, 意味着在template中
            # 可以使用 {% if request.can_do_this %} 语句
            # 而不用这样: {% if request.user.can_do_this or request.user.is_staff %}
            request.can_do_this = True
            return request

        # 返回 Http 403 错误
        raise PermissionDenied

之后我们便可以在FBVs中使用这些工具:

    # myapp/views.py

    from django.shortcuts import get_object_or_404, render

    from .utils import check_rights
    from .models import Article

    def article_list(request):

        request = check_rights(request)

        return render(request,
            "myapp/article_list.html",
            {"articles": Article.objects.all()}
        )

    def article_detail(request, pk):

        article = get_object_or_404(Article, pk=pk)

        request = check_rights(request)

        return render(request,
            "myapp/article_detail.html",
            {"article": article}
        )


    def article_preview(request):
        """
        不使用check_rights()
        """

        articles = Article.objects.all()

        return render(request,
            "myapp/article_preview.html",
            {"articles": articles}
        )

还有一个好处是, 我们可以将这一工具轻易的应用到CBVs中:

    # myapp/views.py

    from django.views.generic import DetailView

    from .utils import check_rights
    from .models import Article

    class ArticleDetail(DetailView):

        model = Article

        def dispatch(self, request, *args, **kwargs):
        request = check_rights(request)
        return super(ArticleDetail, self).dispatch(request, *args, **kwargs)

3. 使用装饰器 (Decorators)

在Python中, decorator使我们的程序简单易懂, 在Django中同样也是这样. 例如django.contrib.auth.decorators.login_required. 当然我们可以自己编写decorator来达到这一目的, 以下是FBVs的decorator模板:

    # 一个简单地decorator模板
    import functools

    def decorator(view_func):
    @functools.wraps(view_func)
    def new_view_func(request, *args, **kwargs):
        # 在此添加修改request (HttpRequest) 的逻辑
        response = view_func(request, *args, **kwargs)
        # 在此添加修改response (HttpResponse) 的逻辑
        return response
    return new_view_func

接下来, 我们按照以上模板, 将之前的check_rights()加以修改:

    # myapp/decorators.py
    import functools

    from . import utils

    def check_rights(view_func):
        @functools.wraps(view_func)
        def new_view_func(request, *args, **kwargs):

            request = utils.check_rights(request)

            response = view_func(request, *args, **kwargs)

            return response
        return new_view_func

然后, 在views.py中使用该decorator:

    # myapp/views.py
    from django.shortcuts import get_object_or_404, render

    from .decorators import check_rights
    from .models import Article

    @check_rights
    def article_detail(request, pk):

        article = get_object_or_404(Article, pk=pk)

        return render(request, "myapp/article_detail.html",
            {"article": article}
        )

最后需要说明的是, decorator虽然好用, 但也不能乱用, 过多的decorator也会造成代码的混论.


原文链接: http://weiguda.com/blog/10/