Django入门与实践-第23章:分页实现(完结)
#从现在起,我们将在 board_topics 这个视图中来操作。 python manage.py shell from django.contrib.auth.models import User from boards.models import Board, Topic, Post user = User.objects.first() board = Board.objects.get(name='Django') for i in range(100): subject = 'Topic test #{}'.format(i) topic = Topic.objects.create(subject=subject, board=board, starter=user) Post.objects.create(message='Lorem ipsum...', topic=topic, created_by=user)
#在我们返回去写代码之前,让我们用 python shell 来做一些更多的实验: python manage.py shell from boards.models import Topic Topic.objects.count() Topic.objects.filter(board__name='Django').count() queryset = Topic.objects.filter(board__name='Django').order_by('-last_updated')
#定义一个你要分页的查询集(QuerySet)的排序是很重要的。否则,会返回给你错误的结果。 #现在让我们导入 Paginator 工具: from django.core.paginator import Paginator paginator = Paginator(queryset, 20) #这里我们告诉Django将查询集按照每页20个元素分页。 paginator.count paginator.num_pages paginator.page_range paginator.page(2) page = paginator.page(2) type(page) type(paginator)
#我们来简单看一下 Page 类提供的属性和方法: page = paginator.page(1) page.has_next() page.has_previous() page.has_other_pages() page.next_page_number()
#这里是我们如何使用 FBV 来实现分页: #boards/views.py from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger def board_topics(request, pk): board = get_object_or_404(Board, pk=pk) queryset = board.topics.order_by('-last_updated').annotate(replies=Count('posts') - 1) page = request.GET.get('page', 1) paginator = Paginator(queryset, 20) try: topics = paginator.page(page) except PageNotAnInteger: topics = paginator.page(1) except EmptyPage: topics = paginator.page(paginator.num_pages) return render(request, 'topics.html', {'board': board, 'topics': topics}) 在 topics HTML列表的基础上,我们可以渲染分页组件: <!--templates/topics.html--> {% if topics.has_other_pages %} <nav aria-label="Topics pagination" class="mb-4"> <ul class="pagination"> {% if topics.has_previous %} <li class="page-item"> <a class="page-link" href="?page={{ topics.previous_page_number }}">Previous</a> </li> {% else %} <li class="page-item disabled"> <span class="page-link">Previous</span> </li> {% endif %} {% for page_num in topics.paginator.page_range %} {% if topics.number == page_num %} <li class="page-item active"> <span class="page-link"> {{ page_num }} <span class="sr-only">(current)</span> </span> </li> {% else %} <li class="page-item"> <a class="page-link" href="?page={{ page_num }}">{{ page_num }}</a> </li> {% endif %} {% endfor %} {% if topics.has_next %} <li class="page-item"> <a class="page-link" href="?page={{ topics.next_page_number }}">Next</a> </li> {% else %} <li class="page-item disabled"> <span class="page-link">Next</span> </li> {% endif %} </ul> </nav> {% endif %}
#GCBV 分页 #下面,相同的实现,但这次使用ListView。 #boards/views.py class TopicListView(ListView): model = Topic context_object_name = 'topics' template_name = 'topics.html' paginate_by = 20 def get_context_data(self, **kwargs): kwargs['board'] = self.board return super().get_context_data(**kwargs) def get_queryset(self): self.board = get_object_or_404(Board, pk=self.kwargs.get('pk')) queryset = self.board.topics.order_by('-last_updated').annotate(replies=Count('posts') - 1) return queryset #myproject/urls.py url(r'^boards/(?P<pk>\d+)/$', views.TopicListView.as_view(), name='board_topics'), #templates/topics.html {% block content %} <div class="mb-4"> <a href="{% url 'new_topic' board.pk %}" class="btn btn-primary">New topic</a> </div> <table class="table mb-4"> <!-- table content suppressed --> </table> {% if is_paginated %} <nav aria-label="Topics pagination" class="mb-4"> <ul class="pagination"> {% if page_obj.has_previous %} <li class="page-item"> <a class="page-link" href="?page={{ page_obj.previous_page_number }}">Previous</a> </li> {% else %} <li class="page-item disabled"> <span class="page-link">Previous</span> </li> {% endif %} {% for page_num in paginator.page_range %} {% if page_obj.number == page_num %} <li class="page-item active"> <span class="page-link"> {{ page_num }} <span class="sr-only">(current)</span> </span> </li> {% else %} <li class="page-item"> <a class="page-link" href="?page={{ page_num }}">{{ page_num }}</a> </li> {% endif %} {% endfor %} {% if page_obj.has_next %} <li class="page-item"> <a class="page-link" href="?page={{ page_obj.next_page_number }}">Next</a> </li> {% else %} <li class="page-item disabled"> <span class="page-link">Next</span> </li> {% endif %} </ul> </nav> {% endif %} {% endblock %}
#可复用的分页模板 #就像我们在 form.html 中封装模板时做的一样,我们也可以为分页的HTML代码片创建类似的东西。 #我们来对主题帖子页面进行分页,进而找到一种复用分页组件的方法法。 #boards/views.py class PostListView(ListView): model = Post context_object_name = 'posts' template_name = 'topic_posts.html' paginate_by = 2 def get_context_data(self, **kwargs): self.topic.views += 1 self.topic.save() kwargs['topic'] = self.topic return super().get_context_data(**kwargs) def get_queryset(self): self.topic = get_object_or_404(Topic, board__pk=self. kwargs.get('pk'), pk=self.kwargs.get('topic_pk')) queryset = self.topic.posts.order_by('created_at') return queryset #更新一下 url.py url(r'^boards/(?P<pk>\d+)/topics/(?P<topic_pk>\d+)/$', views.PostListView.as_view(), name='topic_posts'), 现在,我们从topics.html模板中获取分页部分的html代码片, 并在templates/includes 文件夹下面创建一个名为 pagination.html 的新文件, 和 forms.html 同级目录: #templates/includes/pagination.html {% if is_paginated %} <nav aria-label="Topics pagination" class="mb-4"> <ul class="pagination"> {% if page_obj.has_previous %} <li class="page-item"> <a class="page-link" href="?page={{ page_obj.previous_page_number }}">Previous</a> </li> {% else %} <li class="page-item disabled"> <span class="page-link">Previous</span> </li> {% endif %} {% for page_num in paginator.page_range %} {% if page_obj.number == page_num %} <li class="page-item active"> <span class="page-link"> {{ page_num }} <span class="sr-only">(current)</span> </span> </li> {% else %} <li class="page-item"> <a class="page-link" href="?page={{ page_num }}">{{ page_num }}</a> </li> {% endif %} {% endfor %} {% if page_obj.has_next %} <li class="page-item"> <a class="page-link" href="?page={{ page_obj.next_page_number }}">Next</a> </li> {% else %} <li class="page-item disabled"> <span class="page-link">Next</span> </li> {% endif %} </ul> </nav> {% endif %} #现在,我们在 topic_posts.html 文件中来使用它: #templates/topic_posts.html {% block content %} <div class="mb-4"> <a href="{% url 'reply_topic' topic.board.pk topic.pk %}"class="btn btn-primary" role="button">Reply</a> </div> {% for post in posts %} <div class="card {% if forloop.last %}mb-4{% else %}mb-2{% endif %} {% if forloop.first %}border-dark{% endif %}"> {% if forloop.first %} <div class="card-header text-white bg-dark py-2 px-3">{{ topic.subject }}</div> {% endif %} <div class="card-body p-3"> <div class="row"> <div class="col-2"> <img src="{% static 'img/avatar.svg' %}" alt="{{post.created_by.username }}" class="w-100"> <small>Posts: {{ post.created_by.posts.count }}</small> </div> <div class="col-10"> <div class="row mb-3"> <div class="col-6"> <strong class="text-muted">{{ post.created_by.username }}</strong> </div> <div class="col-6 text-right"> <small class="text-muted">{{ post.created_at}}</small> </div> </div> {{ post.message }} {% if post.created_by == user %} <div class="mt-3"> <a href="{% url 'edit_post' post.topic.board.pk post.topic.pk post.pk %} "class="btn btn-primary btn-sm" role="button">Edit</a> </div> {% endif %} </div> </div> </div> </div> {% endfor %} {% include 'includes/pagination.html' %} {% endblock %}
我们同样也可以更新一下先前的模板,topics.html 模板同样也可以这个封装的分页模板。 #templates/topics.html {% block content %} <div class="mb-4"> <a href="{% url 'new_topic' board.pk %}" class="btn btn-primary">New topic</a> </div> <table class="table mb-4"> <!-- table code suppressed --> </table> {% include 'includes/pagination.html' %} {% endblock %}