资讯专栏INFORMATION COLUMN

Django generics view 以及看源码为什么这么重要

sf_wangchong / 2230人阅读

摘要:代码讲解先贴一波源码,的有很多,就讲下最常见的。这是里面的东东这是里面的东东首先是为什么不需要写判断,而在子类里面定义即可,可以看到是下面的方法做了通用的处理。

关于阅读代码

我们知道,得益于python的语言特性,python的源码是直接可以看的到的,而django是一种大而全的东东,虽然django的文档看似全面,但实际上有些模块写的确实不怎么样,而且平时遇到的需求也是多变的,有的时候你需要实现某种诡异的功能,当然我们可以用google或者做github的搬运工,但是有的时候框架已经给你做了很多事情,你只需要在多做那么一丢丢就能实现某种需求,这个时候看代码比去扒github是快的多的。

先稍微扯一下Django generics view

这玩意儿通常你百度或者google的时候,大部分时间会跟你讲这是Class-based views

第一次接触的时候,我觉得他牛逼的地方在于只要在子类里面定义get post方法,就不需要写类似这种东西了

    if request.method.lower() == "get":
        do_something()
    else:
        do_otherthing()

其实与普通的method view不同的是,他的父类定义了一堆有用的方法,当你知道这些的时候你可以少些超多的代码来做一些屌屌的事情。

来看下使用示例

防止文件被删除,贴几段过来

class BaseMixin(object):
    
    def get_context_data(self,*args,**kwargs):
        context = super(BaseMixin,self).get_context_data(**kwargs)
        try:
            #热门文章
            context["hot_article_list"] = Article.objects.order_by("-view_times")[0:10]
            #导航条
            context["nav_list"] =  Nav.objects.filter(status=0)
            #最新评论
            context["latest_comment_list"] = Comment.objects.order_by("-create_time")[0:10]

        except Exception as e:
            logger.error(u"[BaseMixin]加载基本信息出错")

        return context


class IndexView(BaseMixin,ListView):
    template_name = "blog/index.html"
    context_object_name = "article_list"
    paginate_by = PAGE_NUM #分页--每页的数目
    
    def get_context_data(self,**kwargs):
        #轮播
        kwargs["carousel_page_list"] = Carousel.objects.all()
        return super(IndexView,self).get_context_data(**kwargs)

    def get_queryset(self):
        article_list = Article.objects.filter(status=0)
        return article_list

这是项目原来的地址

如果你之前只写过method的view,你可能会觉得很诡异,为毛这段代码定义了几个属性,重写了几个方法就实现了我之前写的那么一大坨长长的代码?这种时候就必须看源码了,比文档来的直观。

generic view代码讲解

OK先贴一波源码,django的generic view有很多,就讲下最常见的list。

这是 base.py里面的东东

class View(object):
    """
    Intentionally simple parent class for all views. Only implements
    dispatch-by-method and simple sanity checking.
    """

    http_method_names = ["get", "post", "put", "delete", "head", "options", "trace"]

    def __init__(self, **kwargs):
        """
        Constructor. Called in the URLconf; can contain helpful extra
        keyword arguments, and other things.
        """
        # Go through keyword arguments, and either save their values to our
        # instance, or raise an error.
        for key, value in kwargs.iteritems():
            setattr(self, key, value)

    @classonlymethod
    def as_view(cls, **initkwargs):
        """
        Main entry point for a request-response process.
        """
        # sanitize keyword arguments
        for key in initkwargs:
            if key in cls.http_method_names:
                raise TypeError(u"You tried to pass in the %s method name as a "
                                u"keyword argument to %s(). Don"t do that."
                                % (key, cls.__name__))
            if not hasattr(cls, key):
                raise TypeError(u"%s() received an invalid keyword %r" % (
                    cls.__name__, key))

        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            if hasattr(self, "get") and not hasattr(self, "head"):
                self.head = self.get
            return self.dispatch(request, *args, **kwargs)

        # take name and docstring from class
        update_wrapper(view, cls, updated=())

        # and possible attributes set by decorators
        # like csrf_exempt from dispatch
        update_wrapper(view, cls.dispatch, assigned=())
        return view

    def dispatch(self, request, *args, **kwargs):
        # Try to dispatch to the right method; if a method doesn"t exist,
        # defer to the error handler. Also defer to the error handler if the
        # request method isn"t on the approved list.
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed
        self.request = request
        self.args = args
        self.kwargs = kwargs
        return handler(request, *args, **kwargs)

    def http_method_not_allowed(self, request, *args, **kwargs):
        allowed_methods = [m for m in self.http_method_names if hasattr(self, m)]
        logger.warning("Method Not Allowed (%s): %s", request.method, request.path,
            extra={
                "status_code": 405,
                "request": self.request
            }
        )
        return http.HttpResponseNotAllowed(allowed_methods)

这是list.py里面的东东

class MultipleObjectMixin(object):
    allow_empty = True
    queryset = None
    model = None
    paginate_by = None
    context_object_name = None
    paginator_class = Paginator

    def get_queryset(self):
        """
        Get the list of items for this view. This must be an interable, and may
        be a queryset (in which qs-specific behavior will be enabled).
        """
        if self.queryset is not None:
            queryset = self.queryset
            if hasattr(queryset, "_clone"):
                queryset = queryset._clone()
        elif self.model is not None:
            queryset = self.model._default_manager.all()
        else:
            raise ImproperlyConfigured(u""%s" must define "queryset" or "model""
                                       % self.__class__.__name__)
        return queryset

    def paginate_queryset(self, queryset, page_size):
        """
        Paginate the queryset, if needed.
        """
        paginator = self.get_paginator(queryset, page_size, allow_empty_first_page=self.get_allow_empty())
        page = self.kwargs.get("page") or self.request.GET.get("page") or 1
        try:
            page_number = int(page)
        except ValueError:
            if page == "last":
                page_number = paginator.num_pages
            else:
                raise Http404(_(u"Page is not "last", nor can it be converted to an int."))
        try:
            page = paginator.page(page_number)
            return (paginator, page, page.object_list, page.has_other_pages())
        except InvalidPage:
            raise Http404(_(u"Invalid page (%(page_number)s)") % {
                                "page_number": page_number
            })

    def get_paginate_by(self, queryset):
        """
        Get the number of items to paginate by, or ``None`` for no pagination.
        """
        return self.paginate_by

    def get_paginator(self, queryset, per_page, orphans=0, allow_empty_first_page=True):
        """
        Return an instance of the paginator for this view.
        """
        return self.paginator_class(queryset, per_page, orphans=orphans, allow_empty_first_page=allow_empty_first_page)

    def get_allow_empty(self):
        """
        Returns ``True`` if the view should display empty lists, and ``False``
        if a 404 should be raised instead.
        """
        return self.allow_empty

    def get_context_object_name(self, object_list):
        """
        Get the name of the item to be used in the context.
        """
        if self.context_object_name:
            return self.context_object_name
        elif hasattr(object_list, "model"):
            return smart_str("%s_list" % object_list.model._meta.object_name.lower())
        else:
            return None

    def get_context_data(self, **kwargs):
        """
        Get the context for this view.
        """
        queryset = kwargs.pop("object_list")
        page_size = self.get_paginate_by(queryset)
        context_object_name = self.get_context_object_name(queryset)
        if page_size:
            paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size)
            context = {
                "paginator": paginator,
                "page_obj": page,
                "is_paginated": is_paginated,
                "object_list": queryset
            }
        else:
            context = {
                "paginator": None,
                "page_obj": None,
                "is_paginated": False,
                "object_list": queryset
            }
        context.update(kwargs)
        if context_object_name is not None:
            context[context_object_name] = queryset
        return context


class BaseListView(MultipleObjectMixin, View):
    def get(self, request, *args, **kwargs):
        self.object_list = self.get_queryset()
        allow_empty = self.get_allow_empty()
        if not allow_empty and len(self.object_list) == 0:
            raise Http404(_(u"Empty list and "%(class_name)s.allow_empty" is False.")
                          % {"class_name": self.__class__.__name__})
        context = self.get_context_data(object_list=self.object_list)
        return self.render_to_response(context)

首先是为什么不需要写if else判断,而在子类里面定义get post即可,可以看到是下面的dispatch方法做了通用的处理。

    def dispatch(self, request, *args, **kwargs):
        # Try to dispatch to the right method; if a method doesn"t exist,
        # defer to the error handler. Also defer to the error handler if the
        # request method isn"t on the approved list.
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed
        self.request = request
        self.args = args
        self.kwargs = kwargs
        return handler(request, *args, **kwargs)

而通用的listview实际上就是一个父类实现了get方法,其中的MultipleObjectMixin实现了超多的方法供你重写,基本上大部分时间我们重写MultipleObjectMixin里面的对应方法以及设置合适的属性就可以完成大部分的业务逻辑。

BaseListView首先通过 self.get_queryset() 方法拿到了你需要list的对象,中间一坨是分页的东东,下面的context则是最后需要render_to_response时传的参数,这样看就跟以前自己通过method view写的东西一一对应了。而我们只需要根据不同的业务逻辑实现里面的覆盖实现里面的方法即可,有些连覆盖都不需要是要在设置合适的属性,到这里我就不需要根据具体的方法具体讲解了,大家自己看。

class BaseListView(MultipleObjectMixin, View):
    def get(self, request, *args, **kwargs):
        self.object_list = self.get_queryset()
        allow_empty = self.get_allow_empty()
        if not allow_empty and len(self.object_list) == 0:
            raise Http404(_(u"Empty list and "%(class_name)s.allow_empty" is False.")
                          % {"class_name": self.__class__.__name__})
        context = self.get_context_data(object_list=self.object_list)
        return self.render_to_response(context)
一个现实中的例子

上面仅仅是如何使用django默认的各种generic view,但是有时候默认的机制并不能实现你的需求,例如现在在移动端的大潮下,html配饰移动端一般有两种方案:1.响应式;2.两套页面完全分开。两种方案各有利弊,如果我们需要根据根据user agent来做不同终端的页面渲染,甚至是不同的逻辑(web和移动端逻辑不同这很正常),这种时候很容易想到重写dispatch方法,来做到一种通用的处理方式。

from django.views.generic import View as DjangoView

class View(DjangoView):

    def _get_handler(self, request):
        """ 
        根据ua获取handler
        """
        handler_name = request.method.lower()
        if handler_name in self.http_method_names:
            handler = getattr(self, handler_name)
            if not handler:
                return self.http_method_not_allowed

        user_agent = request.META.get("HTTP_USER_AGENT", "").lower()
        handler = getattr(self, handler_name, self.http_method_not_allowed)
        user_agents = ["ipad", "iphone", "ipod", "androidtv", "android"]
        for ua in user_agents:
            if ua in user_agent:
                handler_name = "{}_{}".format(handler_name, ua) 
                break
        return getattr(self, handler_name) or handler
        
    def dispatch(self, request, *args, **kwargs):
        # Try to dispatch to the right method; if a method doesn"t exist,
        # defer to the error handler. Also defer to the error handler if the
        # request method isn"t on the approved list.
        # 支持user agent跳转
        # 如果实现了对应的方法,则直接使用对应ua的规则
        # 例如method=get, ua为iphone的ua,子类实现 get_iphone, 则使用get_iphone进行render
        handler = self._get_handler(request)
        self.request = request
        self.args = args
        self.kwargs = kwargs
        return handler(request, *args, **kwargs)                

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/37619.html

相关文章

  • Django搭建个人博客:基于类的视图

    摘要:有编程基础的同学都知道,类是面向对象技术中非常重要的概念。有区别的是,是作为功能添加到子类中的,而不是作为父类。首先调用父类方法,将这个对象赋值给变量,然后再对其进行统计浏览量的操作,最后将对象返回。 说是完结,马上又开始写进阶篇了。 本章不会为博客项目增加新功能,但是也同样重要,因为我们要学习高逼格的基于类的视图。 什么是类视图 前面章节中写的所有视图都是基于函数的,即def;而类视...

    QiShare 评论0 收藏0
  • django rest framework个人学习笔记(五)————Tutorial3.Class-

    摘要:官网地址个人学习笔记四我们也能够使用来编写我们的,而不是基于正如我们所看到的,这是一个强大的模式,它使我们的功能能够复用,并保持我们的代码整洁。检索,更新或者删除一个实例。接下来将展示使用来组成我们的。 (官网地址)[django rest framework个人学习笔记(四)————Tutorial] Class-based views 我们也能够使用class-based view...

    Sanchi 评论0 收藏0
  • django rest framework个人学习笔记(五)————Tutorial3.Class-

    摘要:官网地址个人学习笔记四我们也能够使用来编写我们的,而不是基于正如我们所看到的,这是一个强大的模式,它使我们的功能能够复用,并保持我们的代码整洁。检索,更新或者删除一个实例。接下来将展示使用来组成我们的。 (官网地址)[django rest framework个人学习笔记(四)————Tutorial] Class-based views 我们也能够使用class-based view...

    wenshi11019 评论0 收藏0
  • django rest framework个人学习笔记(五)————Tutorial3.Class-

    摘要:官网地址个人学习笔记四我们也能够使用来编写我们的,而不是基于正如我们所看到的,这是一个强大的模式,它使我们的功能能够复用,并保持我们的代码整洁。检索,更新或者删除一个实例。接下来将展示使用来组成我们的。 (官网地址)[django rest framework个人学习笔记(四)————Tutorial] Class-based views 我们也能够使用class-based view...

    channg 评论0 收藏0
  • Django REST FrameWork中文教程3:基于类的视图

    摘要:看起来不错再次,它现在仍然非常类似于基于功能的视图。我们还需要重构一下我们使用基于类的视图。中文文档目录中文教程序列化中文教程请求和响应中文教程基于类的视图中文教程验证和权限中文教程关系和超链接中文教程中文教程模式和客户端库 我们也可以使用基于类的视图编写我们的API视图,而不是基于函数的视图。我们将看到这是一个强大的模式,允许我们重用常用功能,并帮助我们保持代码DRY。 使用基于类的...

    UnixAgain 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<