通用类模型ListView的MVT配置

《Django高级实战-开发企业及问答网站》学习笔记3:通用类视图ListView常用属性和方法、ListView源码解析、Python中的多继承-MRO、C3线性化​算法原理。
312阅读 · 2020-7-21 23:49发布

通用类模型ListView的MVT配置

models设置

  • UUID字段
    import uuid
      # 使用UUID模型,主键、默认值为uuid.uuid4,不允许编辑
      uuid_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    
  • 关联外键字段:可以使用settings.AUTH_USER_MODEL或者类对象。

    # 字段user关联USER应用,可以为空,外键被删除时当前字段设置为NULL,方便反查的名称为publisher。
      user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL,related_name="publisher",verbose_name="用户")
    
      # 也可以用如下写法
      # from .models import User
      # user = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL,related_name="publisher",verbose_name="用户")
    
  • 自关联字段:例如文章和评论放在一张表中,该字段相互关联,另外会再使用一个字段区分文章和评论。
    # 关联自己,当关联的外键被删除时,该字段也被删除。
      parent = models.ForeignKey('self', blank=True, null=True, on_delete=models.CASCADE, related_name='thread', verbose_name='自关联')
    
  • 多对多字段:
    liked = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, null=True, related_name='liked_news', verbose_name='点赞用户')
    
  • 布尔字段:
    reply = models.BooleanField(default=False, verbose_name='是否为评论')
    
  • 时间字段:
    created_at = models.DateTimeField(auto_now_add=True, verbose_name'创建时间')
    
  • Meta配置:
    class Meta:
          verbose_name = '首页'
          verbose_name_plural = verbose_name
          ordering = ("-created_at",)  # 以时间倒序排列,值为元组
    
  • models相关函数:

    # 可以根据自己的需要,在models中添加与models相关的方法。
      def __str__(self):
          return self.content
    
      # 判断是否存在parent,并根据结果返回不同内容。**
      def get_parent(self):
          if self.parent:
              return self.parent
          else:
              return self
    

通用类视图ListView

常用方法和属性

  • model:指定关联的模型类(既返回数据表的中的数据)。
  • queryset:用于定义要返回的对象(可以进行简单的过滤)。
  • paginate_by:每页显示的数量。# url中的?page参数
  • page_kwarg:可以指定page参数改为自定义的名称。
  • template_name:关联的前端模板文件,若不指定该字段,则会使用“模型类的小写加_list”作为关联的模板文件。
  • context_object_name:定义QuerySet在模板文件中的名字。默认是“模型类名_list”或“object_list”。
  • ordering:指定排序的字段,可以传入元组类型多字段排序。
  • def get_ordering(self):用于定义较复杂的排序。
  • def get_paginate_by(self,queryset):用于定义较复杂的分页。
  • def get_queryset(self):获取视图的对象列表,返回queryset,重写该方法可以实现较复杂的过滤。默认返回models的所有对象。
  • def get_context_data(self,*,object_list=None,**kwargs):用于添加额外的上下文。
    def get_context_data(self,*,object_list=None,**kwargs):
      context = super.get_context_data()
      context['views'] = 500
      return context
    

template设置

  • 友好标签模板:setting.py中引入'django.contrib.humanize',template中添加{% load static humanize %}。
  • naturaltime:humanize中时间友好显示的标签,使用{{ news.created_at|naturaltime }}可以将2020年7月21日21:00转换显示为“1小时前”。

通用类视图ListView源码解析

  • ListView是列表视图,ListView主要是实现了对对象分页、排序重命名等通用方法。
  • ListView的继承关系如下图(图片中的顺序是从右往左,和实际相反):
  • BaseListView:该类中只定义了get方法,用于响应前端请求。
  • View:django最基本的类视图,该类中定义了8种常用的http方法。
  • MultipleObjectMixin:该类中定义了queryset、paginate_by等属性(可以被重写),以及get_queryset、get_ordering、get_context_data等方法。(以下列出部分方法源码,这里不做细致的记录,如果有需要请自行查阅源码逻辑):

    class MultipleObjectMixin(ContextMixin):
    
          ...省略...
    
          def get_queryset(self):
              """
              Return the list of items for this view.
    
              The return value must be an iterable and may be an instance of
              `QuerySet` in which case `QuerySet` specific behavior will be enabled.
              """
              if self.queryset is not None:
                  queryset = self.queryset
                  if isinstance(queryset, QuerySet):
                      queryset = queryset.all()
              elif self.model is not None:
                  queryset = self.model._default_manager.all()
              else:
                  raise ImproperlyConfigured(
                      "%(cls)s is missing a QuerySet. Define "
                      "%(cls)s.model, %(cls)s.queryset, or override "
                      "%(cls)s.get_queryset()." % {
                          'cls': self.__class__.__name__
                      }
                  )
              ordering = self.get_ordering()
              if ordering:
                  if isinstance(ordering, str):
                      ordering = (ordering,)
                  queryset = queryset.order_by(*ordering)
    
              return queryset
    
          def get_ordering(self):
              """Return the field or fields to use for ordering the queryset."""
              return self.ordering
    
          def get_context_data(self, *, object_list=None, **kwargs):
              """Get the context for this view."""
              queryset = object_list if object_list is not None else self.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
                  }
              if context_object_name is not None:
                  context[context_object_name] = queryset
              context.update(kwargs)
              return super().get_context_data(**context)
    
  • ContextMixin:该类中只定义了get_context_data方法。
  • MutipleObjectTemplateResponseMixin:该类只定义了get_template_names方法,用于获取模板名字。
  • TemplateResponseMixin:该类定义了模板名字等字段,使用render_to_response返回上下文给前端。

扩展知识:Python中的多继承-MRO

ListView通用类中出现了多个继承的情况,如果多个类中都编写了A方法,那么实际调用A方法时哪个类下的方法会被执行呢?

  • MRO全称是方法解析顺序(Method Resolution Order)。它定义了Python中多继承存在的情况下,解释器查找函数解析的具体顺序。
  • Python2.1:只有经典类(Old-style Class),MRO是根据DFS算法计算(深度优先搜索)。
  • Python2.2:引入新式类(New-style Class),新式类使用BFS算法(广度优先搜索)。
  • Python2.3-2.7:经典类和新式类共存,但新式类改为使用C3算法(C3线性化算法)。
  • Python3之后:只有新式类,使用C3算法。

经典类的MRO——DFS算法

  • 使用从左到右的顺序,深度优先遍历类的继承视图。
  • 缺陷:如果遇到菱形继承(既父类B和C都继承了D),靠右继承的类重写了父类方法,但重写方法不会被执行。

新式类的MRO——BFS算法

  • 使用广度优先的顺序,先查找所有父类的函数,再查找父类的父类,以此类推。
  • 缺陷:如果左侧继承的祖父类和右侧的类存在相同方法,调用时会使用右侧类中的方法,违背了单调性(单调性指先从B以及B的父类开始找)。

新式类的MRO——C3算法

  • 从左到右扫描得到的搜索路径,对于每一个节点解释器都会判断该节点是不是好的节点。
  • 好的节点是指,N节点(好的节点)之后的搜索节点中,节点都不继承自N。

扩展知识:C3线性化算法的原理

  • 参考资料:http://kaiyuan.me/2016/04/27/C3_linearization/ ,C3 线性化算法与 MRO
  • C3线性化的方法解析列表公式(本文仅做笔记,不过于细致的讲解,如需理解具体含义请参考上一行的文章):
    L[C(B1⋯BN)]=C+merge(L[B1],⋯,L[BN],B1⋯BN)
    

C3 线性化merge操作步骤

  1. 选取 merge 中的第一个列表记为当前列表 K。
  2. 令 h=head(K),如果 h 没有出现在其他任何列表的 tail 当中,那么将其加入到类 C 的线性化列表中,并将其从 merge 中所有列表中移除,之后重复步骤 2。
  3. 否则,设置 K 为 merge 中的下一个列表,并重复 2 中的操作。
  4. 如果 merge 中所有的类都被移除,则输出类创建成功;如果不能找到下一个 h,则输出拒绝创建类 C 并抛出异常。

C3线性化算法计算例子

  • 演算过程

    L(A) = A + merge(L(O), [O]) = [A,O]
      L(B) = [B,O]
      L(C) = [C,O]
      L(D) = [D,O]
      L(E) = [E,O]
      L(F) = [F,O]
    
      L(K1) = K1 + merge(L(C),L(A),L(B),(C,A,B))
            = K1 + merge([C,O],[A,O],[B,O],(C,A,B))
            = [K1,C] = merge([O],[A,O],[B,O],(A,B))
            = [K1,C,A] = merge([O],[O],[B,O],(B))
            = [K1,C,A,B] = merge([O],[O],[O])
            = [K1,C,A,B,O]
    
      L(K2) = [K2,B,D,E,O]
      L(K3) = [K3,A,D,O]
    
      L(Z)  = Z + merge(L(K1),L(K3),L(K2),(K1,K3,K2))
            = Z + merge(L([K1,C,A,B,O]),L([K3,A,D,O]),L([K2,B,D,E,O]),(K1,K3,K2))
            = [Z,K1] + merge(L([C,A,B,O]),L([K3,A,D,O]),L([K2,B,D,E,O]),(K3,K2))
            = [Z,K1,C] + merge(L([A,B,O]),L([K3,A,D,O]),L([K2,B,D,E,O]),(K3,K2))
            = [Z,K1,C,K3] + merge(L([A,B,O]),L([A,D,O]),L([K2,B,D,E,O]),(K2))
            = [Z,K1,C,K3,A] + merge(L([B,O]),L([D,O]),L([K2,B,D,E,O]),(K2))
            = [Z,K1,C,K3,A,K2] + merge(L([B,O]),L([D,O]),L([B,D,E,O]))
            = [Z,K1,C,K3,A,K2,B] + merge(L([O]),L([D,O]),L([D,E,O]))
            = [Z,K1,C,K3,A,K2,B,D] + merge(L([O]),L([O]),L([E,O]))
            = [Z,K1,C,K3,A,K2,B,D,E] + merge(L([O]),L([O]),L([O]))
            = [Z,K1,C,K3,A,K2,B,D,E,O]