Django предоставляет мощную встроенную конструкцию под названием Paginator, которая помогает упростить обработку разбивки на страницы для ваших моделей и помогает избавиться от шаблонного кода. В этой статье показано, как использовать этот класс во встроенных в Django ListView и DetailView CBV на двух простых примерах.

Примеры моделей

Во-первых, давайте определим класс модели и соответствующее представление без нумерации страниц.

В качестве простого примера давайте представим простой вариант использования упаковки коробок с предметами в каждой коробке. Отношение будет представлено как отношение внешнего ключа «один ко многим» в модели Item, указывающее на модель Box.

class Box(models.model):
  name = models.CharField(max_length=128)
  height = models.IntegerField()
  width = models.IntegerField()
  length = models.IntegerField()
  description = models.TextField()

class Item(models.model):
  box = models.ForeignKey(Box)
  name = models.CharField(max_length=128)
  description = models.TextField()

Далее приведены соответствующие виды без разбивки на страницы для модели Box:

class BoxListView(ListView):
  model = Box

class BoxDetailView(DetailView):
  model = Box
  slug_field = 'id'
  slug_url_kwarg = 'id'
  context_object_name = 'box'

Теперь давайте разобьем оба вида на страницы, начиная с ListView.

Пагинация модели в ListView

Первый шаг — разбить представление Listдля модели Box, чтобы список всех блоков был разбит на страницы по 15 блоков на странице.

К счастью, Django упрощает эту задачу, предоставляя свойство paginate_by в универсальном классе ListView. Ниже приведен такой пример того, как обновить существующий ListView для разбиения на страницы полей в группах по 15:

class BoxListView(ListView):
  model = Box
  paginate_by = 15

Использование свойства paginate_by в CBV открывает доступ к новым контекстным переменным в шаблоне, позволяющим отображать страницы, например:

<ul>
  {% for box in page_obj %}
    <li>{{ box.name }}</li>
  {% endfor %}
</ul>
<br />
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.prev_page_number }}">Previous</a>
{% endif %}
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">Next</a>
{% endif %}

page_obj в цикле for является генератором экземпляров модели Box, и на него можно ссылаться как таковой. Генератор вернет экземпляры только для текущей страницы.

Обратите внимание, что CBV автоматически унаследовал новый параметр HTTP GET, page, представляющий текущий отображаемый номер страницы.

Разбиение на страницы связанной модели в DetailView

Далее более сложная проблема: при отображении страницы сведений для одного поля давайте разобьем на страницы все содержимое этого поля (представленное моделью Item) в контексте представления BoxDetails. Ведь в одной Коробке могут быть сотни предметов.

Ответ заключается в том, чтобы объединить перезапись метода get_context_object в CBV с классом Paginator, встроенным в Django:

class BoxDetailView(DetailView):
    model = Box
    slug_field = 'box_id'
    slug_url_kwarg = 'box_id'
    context_object_name = 'box'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        box = kwargs.get('object')
        page_num = self.request.GET.get('page', 1)
        results = box.item_set.all()
        paginator = Paginator(results, per_page=15)
        context['box_contents'] = paginator.get_page(page_num)
        return context

Обновленный метод добавляет новую переменную в контекстный словарь, отправляемый в шаблон, с именем box_contents. Эта новая переменная является экземпляром объекта Django Page, который, как и в предыдущем примере, содержит все содержимое текущей страницы, а также некоторые метаданные о номере текущей страницы, общем количестве страниц, номерах следующей и предыдущей страниц, и так далее.

В шаблоне новую переменную также можно использовать для печати каждого из элементов в поле только для текущей страницы:

<h1>Viewing details for box {{ box.name }}</h1>
Box dimensions: {{ box.width }}"W x {{ box.length }}"L x {{ box.height }}"H
<br />
Description: {{ box.description }}
<br />
<h2>Box contents:</h2>
<ul>
  {% for item in box_contents %}
    <li>{{ item.name }}</li>
  {% endfor %}
</ul>

Ниже, в том же файле шаблона, можно использовать следующее, чтобы напечатать все номера страниц и выделить текущую страницу жирным шрифтом:

Pages:
{% if page_obj.has_previous %}
  <a href="?page={{ page_obj.prev_page_number }}">Previous</a>
{% endif %}
{% for page_num in box_contents.paginator.page_range %}
  {% if page_num == box_contents.number %}
    <strong>{{ page_num }}</strong>
  {% else %}
    <a href="?page={{ page_num }}">{{ page_num }}</a>
  {% endif %}
{% endfor %}
{% if page_obj.has_next %}
  <a href="?page={{ page_obj.next_page_number }}">Next</a>
{% endif %}

Удачной пагинации! Для получения дополнительных примеров и более глубокого погружения в тему перейдите по ссылкам ниже.

Рекомендуемые чтения

Я нашел следующие страницы чрезвычайно полезными: