Django分页的实现原理

本文主要介绍Django中的分页是如何实现的,首先介绍分页的使用方法,然后介绍其实现原理。

1、使用方法:

VIew视图:

class CategoryArticleListView(BaseMixin,ListView):
    template_name = 'category.html'
    paginate_by = NUM_PER_PAGE
    context_object_name = 'article_list'

    def get_queryset(self):
        article_list = []
        alias = self.kwargs.get('alias')
        try:
            self.category = Category.objects.get(alias=alias)
        except Category.DoesNotExist:
            logger.error('no this category')
        article_list = self.category.article_set.all()
        return article_list

    def get_context_data(self,*args,**kwargs):
        kwargs['category_name'] = self.category.name
        return super(CategoryArticleListView,self).get_context_data(**kwargs)

上面这个视图就实现了,分页。 在html模板中定义:

<ul class="pagination pagination-centered pagination-medium">
        <li><a href="?page=1">首页 </a></li>
    {% if  page_obj.has_previous %}
        <li class="abled">
            <a href="?page={{page_obj.previous_page_number}}"> 上一页</a>
        </li>
    {% else %}
        <li class="disabled">
            <a>上一页</a>
        </li>
    {% endif %}
    {% for page in paginator.page_range %}
     {% if page != page_obj.number %}
        <li><a href="?page={{page}}">{{page}} </a></li>
    {% else %}
     <li class="active"><a>{{page}}</a></li>
    {% endif %}
    {% endfor %}


    {% if  page_obj.has_next%}
        <li class="abled">
            <a href="?page={{page_obj.next_page_number}}">下一页</a>
        </li>
    {% else %}
        <li class="disabled">
            <a>下一页 </a>
        </li>
    {% endif %}
            <li><a href="?page={{paginator.num_pages}}">末页</a></li>
</ul>

在基于类的视图中,关于分页有一个属性最关键:paginate_by。通过定义该属性,就可以得到Paginator,page等上下文,在模板中就可以自由使用这些变量了;如果不定义该属性,就得不到这些变量。关于分页类paginator,page等类的使用方法可以参考Django官方文档的相关部分。

2、实现原理

看到这,可能想问了,为什么定义了一个paginator_by属性就可以得到Paginator等变量呢?现在就要看下Django源码了。

视图中CategoryArticleListView继承ListView,Listview的父类为BastListView,而BaseListView的一个父类是MultipleObjectMixin。现在拿出该类的源码,代码较多,只粘贴一部分。

class MultipleObjectMixin(ContextMixin):

    paginate_by = None
    paginator_class = Paginator
    page_kwarg = 'page'

    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):
        """
        Returns the maximum number of orphans extend the last page by when
        paginating.
        """
        return self.paginate_orphans

    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 '%s_list' % object_list.model._meta.model_name
        else:
            return None

    def get_context_data(self, **kwargs):
        """
        Get the context for this view.
        """
        queryset = kwargs.pop('object_list', 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(MultipleObjectMixin, self).get_context_data(**context)

看该类的get_context_data,因为在子类CategoryArticleListView中调用了父类的get_context_data,因此这个方法是关键。 看这句:

 page_size = self.get_paginate_by(queryset)

调用 get_paginate_by方法。

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

当我们定义了paginate_by的数值,page-size就是一个非None值,就会执行下面;

 paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size)
        context = {
            'paginator': paginator,
            'page_obj': page,
            'is_paginated': is_paginated,
            'object_list': queryset
        }

上下文添加了'paginator', 'page_obj'等变量。

相反,如果不设置paginate_by的值,也就是paginate_by为默认值None,上下文context只有一个object_list,也就是一个查询集。

其实Django的分页实现原理还是比较简单的。

说完了Django,再简单说下Flask。Flask的分页和Django的实现原理是完全不一样的。
Flask也是得到一个关于分页器的一个类, 但它是直接通过Flask-SQlalchemy得到的。

    pagination = Article.query.newest().paginate(page,Article.PER_PAGE)

然后在模板中使用这个变量。

   {% macro paginate(pagination, page_url) %}
    <div class=pagination>
        {%- for page in pagination.iter_pages() %}
      {% if page %}
      {% if page != pagination.page %}
          <a href="/latest/{{ page }}">{{ page }}</a>
      {% else %}
       <strong>{{ page }}</strong>
     {% endif %}
   {% else %}
    <span class=ellipsis>…</span>
   {% endif %}
  {%- endfor %}
   </div>
{% endmacro %}
--------EOF---------
微信分享/微信扫码阅读