Как и многое во фреймворке, стандартные задачи в Django решаются довольно просто стандартными средствами. Трудности возникают разве что при кастомизации стандартных решений. В этой заметке мы рассмотрим лишь пагинацию по одной модели (что в подавляющем большинстве таковой и является).
Итак, представим себе, что мы имеем модель Post
, имеющей примерно следующий вид:
from django.db import models
class Post(models.Model):
name = models.CharField(max_length=100, verbose_name='Название')
slug = models.CharField(max_length=64, verbose_name='URL', validators=[validate_slug], unique=True)
published = models.DateTimeField(default=None, null=True, blank=True)
text = models.TextField(verbose_name='Текст')
Тогда контроллер для вывода списка заметок будет выглядеть примерно следующим образом:
from django.views.generic import ListView
from post.models import Post
class PostListView(ListView):
model = Post
Шаблон же для страницы с листингом заметок будет выглядеть следующим образом (в общем виде):
<div class="post-list">
{% for object in object_list %}
<div class="post">
<h3>{{ object.name }}</h3>
<p>
{{ object.text|truncatewords_html:10 }}
</p>
</div>
{% endfor %}
</div>
Итак, мы описали в общем виде наш MVC.
Чтобы добавить к нашему листингу пагинацию, добавим атрибут paginate_by
для класса контроллера:
from django.views.generic import ListView
from post.models import Post
class PostListView(ListView):
model = Post
paginate_by = 20 # Вот эта строчка
Теперь в контексте нашего шаблона появляется объект page_obj
- объект для страницы с пагинацией.
Теперь давайте его используем для вывода ссылок для навигации между страницами:
<div class="post-list">
{% for object in object_list %}
<div class="post">
<h3>{{ object.name }}</h3>
<p>
{{ object.text|truncatewords_html:10 }}
</p>
</div>
{% endfor %}
<div class="example1-pagination">
{% if page_obj.has_previous %}
<a class="example1-pagination_link" href="?page=1">« первая</a>
<a class="example1-pagination_link" href="?page={{ page_obj.previous_page_number }}">предыдущая</a>
{% endif %}
<span class="example1-pagination_link example1-pagination_link__active">
Страница {{ page_obj.number }} из {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a class="example1-pagination_link" href="?page={{ page_obj.next_page_number }}">следующая</a>
<a class="example1-pagination_link" href="?page={{ page_obj.paginator.num_pages }}">последняя »</a>
{% endif %}
</div>
</div>
Таким образом, мы получаем верстку пагинации примерно следующего формата:
Естественно, можно и иначе вывести пагинацию. Так, к примеру, можно показывать первые три страницы, по три страницы вокруг текущей и три последние страницы. Такой подход мне кажется более удобным для использования:
<div class="post-list">
{% for object in object_list %}
<div class="post">
<h3>{{ object.name }}</h3>
<p>
{{ object.text|truncatewords_html:10 }}
</p>
</div>
{% endfor %}
<div class="example2-pagination">
{% for num in page_obj.paginator.page_range %}
{% if num < page_obj.number and num < 4 %}
<a class="example2-pagination_link" href="?page={{ num }}">{{ num }}</a>
{% elif num < page_obj.number and num > page_obj.number|add:-4 %}
<a class="example2-pagination_link" href="?page={{ num }}">{{ num }}</a>
{% elif num == page_obj.number %}
<span class="example2-pagination_link example2-pagination_link__active">{{ num }}</span>
{% elif num > page_obj.number and num < page_obj.number|add:4 %}
<a class="example2-pagination_link" href="?page={{ num }}">{{ num }}</a>
{% elif num > page_obj.number and num > page_obj.paginator.num_pages|add:-3 %}
<a class="example2-pagination_link" href="?page={{ num }}">{{ num }}</a>
{% endif %}
{% endfor %}
</div>
</div>
Тогда получим примерно следующий вид:
Если же вы используете не Class Based View, а обычные view в виде функций, то подготовить объект для пагинации придётся нам самим:
from django.shortcuts import render
from django.core.paginator import Paginator
from post.models import Post
def post_list_view(request):
paginator = Paginator(Post.objects.all(), per_page=20)
page = paginator.page(request.GET.get('page', 1))
return render(request, 'post/post_list.html', {
'object_list': page.object_list,
'page_obj': page,
})
Подготовив в нашем view таким образом контекст шаблона, нам не нужно беспокоиться об изменениях самих шаблонов, ведь мы передали в них ровно то, что передал бы и его брат-ClassBasedView, что мы рассмотрели выше.