第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

全部開發(fā)者教程

Django 入門教程

課程導(dǎo)學(xué)
Django 慕課教程使用指南
Django開發(fā)實(shí)戰(zhàn)
35 開發(fā)實(shí)戰(zhàn)

Django 的 ListView 類視圖詳解

本小節(jié)將繼續(xù)介紹 Django 中常用的 ListView 類視圖并深入分析其實(shí)現(xiàn)原理,最后達(dá)到完全掌握該視圖類的目的。

1. ListView 類視圖介紹和使用

ListView 類從名字上看應(yīng)該是處理和列表相關(guān)的視圖,事實(shí)也是如此。我們同樣基于前面 TemplateView 中實(shí)現(xiàn)的例子,使用 ListView 來(lái)減少代碼,體驗(yàn)下 ListView 視圖類給我們帶來(lái)的便捷。

實(shí)驗(yàn)1:重現(xiàn) TemplateView 功能;

首先我們完成前面 TemplateView 的簡(jiǎn)單功能,然后在提出幾個(gè)報(bào)錯(cuò)的問題,這些問題比較簡(jiǎn)單,只要看下報(bào)錯(cuò)位置和源碼信息就非常清楚了。

首先我先給出一個(gè)基本的知識(shí):ListView 具備 TemplateView 所有的功能與屬性,并做了許多擴(kuò)展。那么前面由TemplateView 實(shí)現(xiàn)的所有示例直接將 TemplateView 替換成 ListView 也是可以運(yùn)行的?

我們以最簡(jiǎn)單的一個(gè)模板例子進(jìn)行演示:

hello_app/views.py 中新增一個(gè)視圖類 TestListView1:

(django-manual) [root@server first_django_app]# cat templates/test1.html 
<p>{{ content }}</p>
<div>{{ spyinx.age }}</div>
class TestListView1(ListView):
    template_name = 'test1.html'

hello_app/urls.py 中新增一個(gè) URLConf 配置:

urlpatterns = [
    # ...

    path('test_list_view1/', views.TestListView1.as_view(extra_context=context_data), name='test_list_view1')
]

使用 runserver 命令啟動(dòng)后,請(qǐng)求對(duì)應(yīng)的 URL 地址,發(fā)現(xiàn)異常,錯(cuò)誤原因也很明顯,缺少queryset。

圖片描述

上面的出錯(cuò)是在父類的 get() 方法中,那么修改 hello_app/views.py 位置的視圖類 TestListView1,重新定義自己的 get() 方法,如下:

class TestListView1(ListView):
    template_name = 'test1.html'
    def get(self, request, *args, **kwargs):
       return self.render_to_response(context={'content': '正文1', 'spyinx': {'age': 29}})

啟動(dòng)服務(wù)后同樣報(bào)錯(cuò),不過這次錯(cuò)誤不一樣了,如下:

圖片描述

同樣顯示的是沒有對(duì)象列表。我們通過查看源碼也能輕易解決這個(gè)問題。這個(gè)問題留到后面分析原源碼的時(shí)候去解決?,F(xiàn)在直接給出兩個(gè)報(bào)錯(cuò)的解決方案,如下:

# 解決第一個(gè)沒有自定義get()函數(shù)報(bào)錯(cuò)
class TestListView1(ListView):
    template_name = 'test1.html'
    queryset = Member.objects.all()
    # 另一種寫法也是可以的
    # model = Member
    
# 解決第二個(gè)自定義get()函數(shù)報(bào)錯(cuò)
class TestListView1(ListView):
    template_name = 'test1.html'
    object_list = Member.objects.all()
    def get(self, request, *args, **kwargs):
       return self.render_to_response(context={'content': '正文1', 'spyinx': {'age': 29}})

最后正確的結(jié)果如下,這里直接用 curl 命令請(qǐng)求結(jié)果顯示即可。

[root@server ~]# curl http://127.0.0.01:8888/hello/test_list_view1/
<p>正文1</p>
<div>29</div>

實(shí)驗(yàn)2:簡(jiǎn)化分頁(yè)代碼。同樣前面 TemplateView 做的那個(gè)顯示會(huì)員列表的基礎(chǔ)上,簡(jiǎn)化原來(lái)的代碼。

準(zhǔn)備原來(lái)的模板文件,修改分頁(yè)那塊代碼:

(django-manual) [root@server first_django_app]# cat templates/test.html
<html>
<head>
<style type="text/css">
    .page{
       margin-top: 10px;
       font-size: 14px;
    }

    .member-table {
       width: 50%;
       text-align: center;
    }
</style>
</head>
<body>

<p>會(huì)員信息-第{{ page_obj.number }}頁(yè)/共{{ paginator.num_pages }}頁(yè), 每頁(yè){{ paginator.per_page }}條, 總共{{ paginator.count }}條</p>
<div>
<table border="1" class="member-table">
  <thead>
  <tr>
    <th>姓名</th>
    <th>年齡</th>
    <th>性別</th>
    <th>職業(yè)</th>
    <th>所在城市</th>
  </tr>
  </thead>
  <tbody>
  {% for member in members %}
  <tr>
    <td>{{ member.name }}</td>
    <td>{{ member.age }}</td>
    {% if member.sex == 0 %}
    <td>男</td>
    {% else %} 
    <td>女</td>
    {% endif %}
    <td>{{ member.occupation }}</td>
    <td>{{ member.city }}</td>
  </tr>
  {% endfor %}
   </tbody>
</table>
<div >
<div class="page">
</div>
</div>
</div>

</body>

</html>

添加一個(gè)新的 ListView 視圖類,如下:

class TestListView2(ListView):
    template_name = 'test.html'
    model = Member
    queryset=Member.objects.all()
    paginate_by = 10
    ordering = ["-age"]
    context_object_name = "members"

注意:ordering 是設(shè)置顯示列表的排序字段,字符串前面的 “-” 號(hào)表示的是按照這個(gè)字段倒序排列,可以設(shè)置多個(gè)排序字段。context_object_name 一定要設(shè)置,對(duì)應(yīng)模板文件中的列表數(shù)據(jù)名。

添加 URLConf 配置:

urlpatterns = [  
    # ...
    path('test_list_view2/', views.TestListView2.as_view(), name='test_list_view2')
]

啟動(dòng) first_django_app 工程,從瀏覽器上直接訪問這個(gè) url,就能看到和前面差不多的結(jié)果了??梢詡魅?page 參數(shù)控制第幾頁(yè),但是頁(yè)大小在視圖中已經(jīng)固定,無(wú)法改變。

圖片描述

從這個(gè)簡(jiǎn)單的例子,我們可以看到,相比前面用 TemplateView 手工對(duì)數(shù)據(jù)進(jìn)行分頁(yè),這里的 ListView 內(nèi)部已經(jīng)給我們實(shí)現(xiàn)了這樣的功能。我們只需要簡(jiǎn)單的配置下,設(shè)置好相關(guān)屬性,就能夠?qū)崿F(xiàn)對(duì)表的分頁(yè)查詢,這樣能節(jié)省重復(fù)的代碼操作,讓項(xiàng)目看起來(lái)簡(jiǎn)潔優(yōu)雅。但是我們一定要了解背后實(shí)現(xiàn)的邏輯,能看得懂源碼,這樣每一步的報(bào)錯(cuò),我們都能在源碼中找到原因,并迅速解決問題。接下來(lái)就是對(duì) ListView 視圖類源碼的學(xué)習(xí)與分析。

2. ListView 類視圖深入分析

首先在 VScode 中整體看看 ListView 的源代碼,其源碼路徑為: djnago/views/generic/list.py。來(lái)看看ListView 類的整體繼承關(guān)系:

圖片描述

在紅框中出現(xiàn)的對(duì)象我們是在 TemplateView 中已經(jīng)遇到過了。這里可以看到 ListView 繼承的比 TemplateView 要多且復(fù)雜。我們來(lái)一個(gè)個(gè)分析這些基礎(chǔ)的類。

2.1 MultipleObjectTemplateResponseMixin

首先來(lái)看 MultipleObjectTemplateResponseMixin 這個(gè)對(duì)象,它是一個(gè) Mixin。前面我們提到,一個(gè) Mixin 就是一個(gè)包含一個(gè)或多個(gè)功能片段的對(duì)象。這里的 Mixin 是用于響應(yīng)模板文件和展示列表數(shù)據(jù)的,它繼承至前面介紹到的 TemplateResponseMixin,在 TemplateResponseMixin 上做的擴(kuò)展就是重寫了 get_template_names() 方法,其源碼如下:

class MultipleObjectTemplateResponseMixin(TemplateResponseMixin):
    """Mixin for responding with a template and list of objects."""
    template_name_suffix = '_list'

    def get_template_names(self):
        """
        Return a list of template names to be used for the request. Must return
        a list. May not be called if render_to_response is overridden.
        """
        try:
            names = super().get_template_names()
        except ImproperlyConfigured:
            # If template_name isn't specified, it's not a problem --
            # we just start with an empty list.
            names = []

        # If the list is a queryset, we'll invent a template name based on the
        # app and model name. This name gets put at the end of the template
        # name list so that user-supplied names override the automatically-
        # generated ones.
        if hasattr(self.object_list, 'model'):
            opts = self.object_list.model._meta
            names.append("%s/%s%s.html" % (opts.app_label, opts.model_name, self.template_name_suffix))
        elif not names:
            raise ImproperlyConfigured(
                "%(cls)s requires either a 'template_name' attribute "
                "or a get_queryset() method that returns a QuerySet." % {
                    'cls': self.__class__.__name__,
                }
            )
        return names

從這里的代碼,我們可以解釋第一個(gè)實(shí)驗(yàn)中,第二次添加 get() 方法后報(bào)錯(cuò)的原因,就在這個(gè)代碼段里。首先看這個(gè) get() 函數(shù):

 def get(self, request, *args, **kwargs):
       return self.render_to_response(context={'content': '正文1', 'spyinx': {'age': 29}})

這個(gè) get() 函數(shù)調(diào)用 self.render_to_response() 方法時(shí)會(huì)調(diào)用這個(gè) get_template_names() 方法。如果是在 TemplateView 中,直接這樣寫是毫無(wú)問題的,但是在 ListView 中,ListView 繼承了這個(gè) Mixin,然后調(diào)用的get_template_names() 方法正是這里的代碼。這個(gè) get_template_names() 方法相比原來(lái)的就是多了下半部分代碼,在程序執(zhí)行到下面的語(yǔ)句時(shí),由于沒有 object_list 屬性值就會(huì)觸發(fā)異常:

if hasattr(self.object_list, 'model'):

修正的方法很簡(jiǎn)單,只要一開始加上這個(gè) object_list 屬性值即可。對(duì)于這個(gè)object_list 屬性,它其實(shí)從名字也能看出來(lái),表示一個(gè)對(duì)象的列表值,其實(shí)是一個(gè) QuerySet 結(jié)果集。大概知道這些之后,我們就能理解后面的代碼了:

        if hasattr(self.object_list, 'model'):
            opts = self.object_list.model._meta
            names.append("%s/%s%s.html" % (opts.app_label, opts.model_name, self.template_name_suffix))
        elif not names:
            raise ImproperlyConfigured(
                "%(cls)s requires either a 'template_name' attribute "
                "or a get_queryset() method that returns a QuerySet." % {
                    'cls': self.__class__.__name__,
                }
            )

對(duì)于這段代碼指的是,如果self.object_list 對(duì)應(yīng)著一個(gè)模型時(shí),代碼會(huì)在 names 中添加一個(gè)默認(rèn)的模板文件名,我們可以在 shell 模式下理解下這些代碼:

(django-manual) [root@server first_django_app]# python manage.py shell
Python 3.8.1 (default, Dec 24 2019, 17:04:00) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from hello_app.models import Member
>>> object_list = Member.objects.all()
>>> object_list.model
<class 'hello_app.models.Member'>
>>> object_list.model._meta
<Options for Member>
>>> opts = object_list.model._meta
>>> opts.app_label
'hello_app'
>>> opts.model_name
'member'

這就很明顯了,最后 names 中會(huì)加上一個(gè)額外的元素:hello_app/member_list.html。現(xiàn)在我們可以立馬做一個(gè)實(shí)驗(yàn),將實(shí)驗(yàn)1中的 template_name 屬性值去掉,然后將原來(lái)的 test1.html 拷貝一份放到 template/hello_app 目錄下,操作如下:

(django-manual) [root@server first_django_app]# mkdir templates/hello_app
(django-manual) [root@server first_django_app]# cp templates/test1.html templates/hello_app/member_list.html
class TestListView1(ListView):
    # template_name = 'test1.html'
    model = Member

啟動(dòng)服務(wù),然后運(yùn)行發(fā)現(xiàn)也能成功。這就算對(duì)這個(gè) Mixin 掌握了,我們也理解了它的代碼內(nèi)容并獨(dú)立根據(jù)這個(gè)代碼內(nèi)容完成了一個(gè)實(shí)驗(yàn)。

(django-manual) [root@server first_django_app]# curl http://127.0.0.1:8888/hello/test_list_view1/
<p>正文1</p>
<div>29</div>

2.2 MultipleObjectMixin

這個(gè) Mixin 是用來(lái)幫助視圖處理多個(gè)對(duì)象的,如列表展示,分頁(yè)查詢都是在這里。這也是 ListView 視圖類的核心所在。來(lái)看看源代碼里面關(guān)于這個(gè) Mixin 的屬性和方法:

圖片描述

屬性

  • allow_empty: 是否允許對(duì)象列表為空,默認(rèn)為 True;

  • queryset:對(duì)象的查詢集;

  • model:關(guān)聯(lián)的模型;

  • paginate_by: 分頁(yè)大??;

  • paginate_orphans: 這個(gè)比較有意思,需要通過代碼來(lái)詳細(xì)解釋下其含義。

    # 源碼位置 django/core/paginator.py
    
    class Paginator:
    
        def __init__(self, object_list, per_page, orphans=0,
                     allow_empty_first_page=True):
            self.object_list = object_list
            self._check_object_list_is_ordered()
            self.per_page = int(per_page)
            self.orphans = int(orphans)
            self.allow_empty_first_page = allow_empty_first_page
    
        # ...
    
        def page(self, number):
            """Return a Page object for the given 1-based page number."""
            number = self.validate_number(number)
            bottom = (number - 1) * self.per_page
            top = bottom + self.per_page
            #這里可以看出 self.orphans 的含義
            if top + self.orphans >= self.count:
                top = self.count
            return self._get_page(self.object_list[bottom:top], number, self)
        
        # ...
    

    上面的 page() 方法是根據(jù)傳入的 number 獲取第幾頁(yè)數(shù)據(jù),每頁(yè)的大小由 per_page 屬性確定。我們?cè)谇懊娴?TemplateView 中的分頁(yè)實(shí)例中知道,想要獲取第幾頁(yè)的數(shù)據(jù),可以按照如下公式:

    # 起始位置,number從1開始
    start =  (number - 1) * per_page
    # 結(jié)束位置,不包括end 
    end = number * per_page
    # 另一種簡(jiǎn)單寫法 end = start + per_page
    # 數(shù)據(jù)切片,取第number頁(yè)數(shù)據(jù)
    object_list[start:end]
    

    orphans 屬性的含義就體現(xiàn)在下面兩行代碼中:

    if top + self.orphans >= self.count:
        top = self.count
    

    這里的含義是,如果計(jì)算出的下一頁(yè)的位置加上這個(gè) orphans 屬性的值大于等于對(duì)象的總數(shù),也就是說(shuō)下一頁(yè)的數(shù)據(jù)如果少于 orphans 的值,那么當(dāng)前這一頁(yè)需要把下一頁(yè)剩余的元素都選中。舉個(gè)例子,假設(shè)與102個(gè)數(shù)據(jù),現(xiàn)在按照每頁(yè)展示10條數(shù)據(jù),當(dāng)我展示到第10頁(yè)是,元素的位置應(yīng)該是 90-99,作為切片的話,應(yīng)該是[90:100],即bottom=90, top=100 。假設(shè)我設(shè)置orphans=3,那么有100 + 3 > 102,即最后一頁(yè)數(shù)目少于3個(gè),因此通過上面的邏輯判斷后,top=102,此時(shí)顯示的列表切片為 [90:102]。

  • context_object_name:這個(gè)設(shè)置上下文中對(duì)象列表名稱。我們來(lái)翻看源代碼,查看這個(gè)屬性的含義,如下。

    # 源碼位置:django/views/generic/list.py
    
    class MultipleObjectMixin(ContextMixin):
        # ...
        
        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 '%s_list' % object_list.model._meta.model_name
            else:
                return None
        
        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)
        
        # ...   
    

    查看源碼可以知道,這個(gè)屬性不設(shè)置也是有默認(rèn)值的(注意:只有在 object_list 沒設(shè)置,或者不是 QuerySet 時(shí),才返回 None 值)。通過 get_context_data() 代碼中的這樣一條語(yǔ)句:

    context[context_object_name] = queryset
    

    這樣,在模板文件中,我們就可以使用 context_object_name 變量來(lái)循環(huán)顯示我們的對(duì)象列表了。

  • paginator_class:用于分頁(yè)的類,這種寫法讓 django 的分頁(yè)變得可擴(kuò)展,我們可以提供這樣的分頁(yè)類來(lái)替換掉 Django 中原有的分頁(yè)機(jī)制,從而實(shí)現(xiàn)我們自己的分頁(yè)控制。這種做法在可擴(kuò)展的模式中用的非常多,不過需要仔細(xì)研讀分頁(yè)的源碼,需要定義的屬性和方法才能替換官方的分頁(yè)類。

  • page_kwarg: 查詢頁(yè)號(hào)的 key 值。這個(gè)是指,查詢的頁(yè)號(hào)是從獲取參數(shù)的這個(gè) key 值中取出來(lái)的,可以是在 URLConf 配置中設(shè)定,也可以通過 GET 請(qǐng)求帶參數(shù)傳遞過來(lái)。來(lái)看看源碼里面如何使用這個(gè)屬性的,具體如下。

    # 源碼路徑:django/views/generic/list.py
    
    class MultipleObjectMixin(ContextMixin):
        # ...
        
        def paginate_queryset(self, queryset, page_size):
            # ...
            page_kwarg = self.page_kwarg
            page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1
            # ...
            
        # ...
    
  • ordering:這個(gè)屬性是用來(lái)設(shè)置查詢列表的排序,可以放入多個(gè)排序字段。比如 ordering = ['name'],表示結(jié)果集按照 name 字段從小到大排序,如果想按照倒序的順序,直接用 ordering = ['-name'] 即可。

方法

get_queryset():返回視圖的對(duì)象列表:

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

在這里,代碼邏輯也是非常清楚的。首先是需要理解 Django 的 ORM 操作。在一開始設(shè)置了 queryset 屬性時(shí),如果直接是 QuerySet 的實(shí)例,則會(huì)將用 .all() 將所有的數(shù)據(jù)取出來(lái),得到 queryset 并返回。當(dāng)然這里看代碼,不設(shè)置 queryset 也是可以的,設(shè)置好關(guān)聯(lián)的模型屬性 model,然后通過模型的默認(rèn) manager 調(diào)用 .all() 方法也能實(shí)現(xiàn)同樣的目標(biāo)。最后,在這里還使用了 ordering 的屬性值,如果有設(shè)置,則直接用 QuerySet 的 .ordering() 方法。

幾個(gè)簡(jiǎn)單的獲取屬性值得方法,如下:

  • get_ordering():獲取排序字段;
  • get_paginate_by(): 獲取分頁(yè)大??;
  • get_paginator():實(shí)例化分頁(yè)對(duì)象,關(guān)聯(lián) paginator_class 屬性值;
  • get_paginate_orphans(): 獲取 paginate_orphans 這個(gè)屬性值;
  • get_allow_empty():獲取 allow_empty 這個(gè)屬性值;
  • get_context_object_name():處理 context_object_name 這個(gè)屬性值;
class MultipleObjectMixin(ContextMixin):
    # ...
    
    def get_ordering(self):
        """Return the field or fields to use for ordering the queryset."""
        return self.ordering
    
    # ...
    
    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, **kwargs):
        """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, **kwargs)
    
    def get_paginate_orphans(self):
        """
        Return the maximum number of orphans extend the last page by when
        paginating.
        """
        return self.paginate_orphans
    
    def get_allow_empty(self):
        """
        Return ``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 '%s_list' % object_list.model._meta.model_name
        else:
            return None
        
    # ...
  • paginate_queryset():獲取分頁(yè)數(shù)據(jù)以及分頁(yè)信息。該函數(shù)會(huì)被 get_context_data() 方法調(diào)用生成上下文數(shù)據(jù),用于填充模板中的變量?jī)?nèi)容。該部分源碼會(huì)結(jié)合 get_context_data() 方法一起在下一小節(jié)中詳細(xì)介紹到;

  • get_context_data():獲取渲染模板的上下文數(shù)據(jù),也即分頁(yè)列表元素、分頁(yè)信息等,在下一部分內(nèi)容會(huì)詳細(xì)介紹該函數(shù)中的內(nèi)容。

2.3 BaseListView

講完上面的 MultipleObjectMixin 對(duì)象,ListView 視圖的基本功能其實(shí)就分析完了。 BaseListView 類繼承了 View 和 MultipleObjectMixin,并多添加了一個(gè) get() 方法。這也是 ListView 能直接處理 get 請(qǐng)求的原因。實(shí)驗(yàn)1中的第一個(gè)報(bào)錯(cuò)也是源自這里:self.object_list = self.get_queryset() 。只有定義了 queryset 或者 model屬性時(shí),才能正常執(zhí)行下去。

class BaseListView(MultipleObjectMixin, View):
    """A base view for displaying a list of objects."""
    def get(self, request, *args, **kwargs):
        # 獲取對(duì)象列表
        self.object_list = self.get_queryset()
        # 是否設(shè)置允許為空
        allow_empty = self.get_allow_empty()

        if not allow_empty:
            # 下面的if用于判斷數(shù)據(jù)是否為空,然后相應(yīng)設(shè)置is_empty值
            if self.get_paginate_by(self.object_list) is not None and hasattr(self.object_list, 'exists'):
                is_empty = not self.object_list.exists()
            else:
                is_empty = not self.object_list
            # 在不許為空的條件中,如果為空直接拋出404異常
            if is_empty:
                raise Http404(_("Empty list and '%(class_name)s.allow_empty' is False.") % {
                    'class_name': self.__class__.__name__,
                })
        # 獲取分頁(yè)相關(guān)的數(shù)據(jù)
        context = self.get_context_data()
        # 渲染模板并返回
        return self.render_to_response(context)

圖片描述

可以看到,這段代碼執(zhí)行的過程非常簡(jiǎn)單,很容易能看懂,我已經(jīng)在上面做好了簡(jiǎn)單的注釋。這段代碼中最重要的部分就在這一句中: context = self.get_context_data()。這段代碼是要獲取相應(yīng)的分頁(yè)數(shù)據(jù)結(jié)果,然后調(diào)用 self.render_to_response(context) 來(lái)返回經(jīng)過渲染的模板文件,最后就是我們看到的那個(gè)會(huì)員列表頁(yè)面。self.get_context_data() 方法就是上面的 Mixin 提供的,函數(shù)源碼如下:

    def get_context_data(self, *, object_list=None, **kwargs):
        """Get the context for this view."""
        
        # 獲取對(duì)象列表
        queryset = object_list if object_list is not None else self.object_list
        # 獲取分頁(yè)大小
        page_size = self.get_paginate_by(queryset)
        # 獲取context_object_name,我們實(shí)驗(yàn)2中設(shè)置的就是members,對(duì)應(yīng)著模板中的變量
        context_object_name = self.get_context_object_name(queryset)
        if page_size:
            # 核心的處理就是這一句,根據(jù)指定的分頁(yè)大小對(duì)數(shù)據(jù)集進(jìn)行分析,返回分頁(yè)的對(duì)象列表,分頁(yè)信息、是否分頁(yè)等
            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:
            # 沒有設(shè)置分頁(yè)大小,就是獲取全部數(shù)據(jù),不進(jìn)行分頁(yè)
            context = {
                'paginator': None,
                'page_obj': None,
                'is_paginated': False,
                'object_list': queryset
            }
        # 設(shè)置模板中對(duì)象列表變量的數(shù)據(jù)
        if context_object_name is not None:
            context[context_object_name] = queryset
        # context中再添加額外傳入的數(shù)據(jù)
        context.update(kwargs)
        # 最后調(diào)用父類的get_context_data()方法并返回
        return super().get_context_data(**context)

上面的獲取上下文數(shù)據(jù)的代碼也比較簡(jiǎn)單,有分頁(yè)大小就調(diào)用self.paginate_queryset() 方法查詢分頁(yè)數(shù),沒有分頁(yè)大小就使用全部對(duì)象列表,然后構(gòu)造 context 值,最后調(diào)用父類的 get_context_data() 方法并返回??梢钥吹剑麄€(gè)獲取上下文數(shù)據(jù)的最核心處理就是 self.paginate_queryset() 這個(gè)方法了。它也是由上面介紹的那個(gè) Mixin 提供的,代碼如下:

    def paginate_queryset(self, queryset, page_size):
        """Paginate the queryset, if needed."""
        
        # 核心處理就是這一句
        paginator = self.get_paginator(
            queryset, page_size, orphans=self.get_paginate_orphans(),
            allow_empty_first_page=self.get_allow_empty())
        # 查詢第幾頁(yè)的key值,默認(rèn)是"page"
        page_kwarg = self.page_kwarg
        # 第幾頁(yè)的值會(huì)從kwargs或者GET請(qǐng)求中獲取,對(duì)應(yīng)的key就是上面的page_kwarg,沒有就默認(rèn)為1
        page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1
        try:
            # 會(huì)強(qiáng)制轉(zhuǎn)成int
            page_number = int(page)
        except ValueError:
            # 當(dāng)強(qiáng)制轉(zhuǎn)換異常時(shí),處理邏輯也很清晰
            if page == 'last':
                page_number = paginator.num_pages
            else:
                raise Http404(_("Page is not 'last', nor can it be converted to an int."))
        try:
            # 生成對(duì)應(yīng)的page實(shí)例。一切正常時(shí),返回我們所需要的數(shù)據(jù),否則拋出異常
            page = paginator.page(page_number)
            return (paginator, page, page.object_list, page.has_other_pages())
        except InvalidPage as e:
            raise Http404(_('Invalid page (%(page_number)s): %(message)s') % {
                'page_number': page_number,
                'message': str(e)
            })

當(dāng)這部分代碼能看懂時(shí),前面實(shí)驗(yàn)2部分的整個(gè)內(nèi)部邏輯,你差不多也就弄清楚了。雖然我們看著只需要配置幾個(gè)屬性,但是在 Django 內(nèi)部是替我們做了許多工作的,如果這部分工作并不是你想要的,這時(shí)候,就需要依據(jù)你自己的業(yè)務(wù)邏輯重寫相應(yīng)的函數(shù)了。如果能掌握整個(gè) ListView 視圖的執(zhí)行流程,在繼承它的時(shí)候就會(huì)感到胸有成竹,有錯(cuò)了就去根據(jù)錯(cuò)誤提示追蹤下源碼,這樣就不會(huì)再碰到錯(cuò)誤時(shí),不知道從何下手。所以,閱讀源碼是在學(xué)習(xí) Django 這樣的 Web 框架時(shí),非常重要的一個(gè)技能,而且很多關(guān)于 Django 的功能和用法我們都可以通過源碼來(lái)獲取。

最后,我們來(lái)看看 ListView 的代碼,其實(shí)就是單純繼承前面那個(gè)處理多個(gè)對(duì)象的 Mixin 和這個(gè) BaseListView:

class ListView(MultipleObjectTemplateResponseMixin, BaseListView):
    """
    Render some list of objects, set by `self.model` or `self.queryset`.
    `self.queryset` can actually be any iterable of items, not just a queryset.
    """

3. 小結(jié)

本節(jié)中,我們使用 ListView 完成了兩個(gè)小實(shí)驗(yàn),對(duì) ListView 有了一個(gè)基本的了解。接下來(lái),我們深入學(xué)習(xí)了和 ListView 相關(guān)的類和 mixin,并在源碼學(xué)習(xí)中完成了幾個(gè)簡(jiǎn)單的實(shí)驗(yàn)。在完成本節(jié)學(xué)習(xí)后,是不是對(duì) ListView 有了全新的了解?之后使用 ListView 報(bào)錯(cuò)后,是不是能迅速找到問題所在?如果是的話,那么本節(jié)也算起了一點(diǎn)小小的作用,作為本文作者,我也將感到無(wú)比榮耀。