Добавляем пагинацию (pagination)
На этом занятии мы рассмотрим возможность разбивки списка данных на отдельные страницы. Например, у нас имеется API-запрос на список известных женщин:
Понятно, что в БД может быть огромное количество записей по женщинам и выдавать их все по данному запросу было бы крайне неразумно из-за слишком большого объема данных. Для этого и вводится пагинация, то есть, разбивка на отдельные страницы по несколько записей на каждой. И как это сделать в рамках Django REST Framework мы сейчас увидим.
Конечно, в официальной документации можно подробно почитать, как подключается пагинация к проекту:
Я прямо буду ей следовать. Фактически, все что нам нужно, это прописать в словаре REST_FRAMEWORK, следующие строчки:
REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', 'PAGE_SIZE': 2, . }
Ключ ‘DEFAULT_PAGINATION_CLASS’ позволяет задать класс пагинации, который будет применяться по умолчанию к выдаваемым спискам данных. В Django REST Framework имеется встроенный класс LimitOffsetPagination и мы его здесь указываем. Второй ключ ‘PAGE_SIZE’ задает число записей (в нашем случае женщин) на странице. Я указал два, чтобы мы могли видеть пагинацию в действии.
Запустим, теперь, тестовый веб-сервер:
python manage.py runserver
и откроем в браузере страницу:
Как видите, у нас здесь с вами отобразился только первый фрагмент списка из двух записей. Кроме того, в JSON-ответе мы видим параметры:
- «count» – общее число записей;
- «next» – ссылка на следующую страницу;
- «previous» – ссылка на предыдущую страницу;
- «results» – набор данных.
Также средства DRF позволяют нам в интерактивном режиме в браузере переключаться по страницам и просматривать их содержимое. Однако, обратите внимание, такая пагинация будет автоматически применяться к каждому API-запросу, где идет вывод списка данных. Например, у нас есть запрос для списка зарегистрированных пользователей: http://127.0.0.1:8000/api/v1/auth/users/ и здесь мы тоже видим включенный режим пагинации. Но, если открыть страницу с одной записью: http://127.0.0.1:8000/api/v1/women/9/ то никакой пагинации не будет, т.к. здесь нет списка записей в ответе.
Пользовательские классы пагинации
Конечно, нам может потребоваться для определенных API-запросов настраивать свои параметры пагинации. Это делается достаточно просто с помощью определения собственных классов пагинации и подключения их к нужным видам. Давайте сделаем это для класса WomenAPIList. Вначале определим свой класс пагинации WomenAPIListPagination, который унаследуем от базового класса в DRF – PageNumberPagination:
class WomenAPIListPagination(PageNumberPagination): page_size = 3 page_size_query_param = 'page_size' max_page_size = 10000
- page_size – число записей на страницу;
- page_size_query_param – параметр запроса, в котором можно настраивать количество выдаваемых записей на страницу;
- max_page_size – максимальное количество записей на странице для запроса page_size_query_param.
class WomenAPIList(generics.ListCreateAPIView): queryset = Women.objects.all() serializer_class = WomenSerializer permission_classes = (IsAuthenticatedOrReadOnly, ) pagination_class = WomenAPIListPagination
и в браузере откроем страницу: http://127.0.0.1:8000/api/v1/women/ Видим, что выдается по три записи на странице, как это и настроено в нашем пользовательском классе. Но мы также можем выполнить и такой запрос: http://127.0.0.1:8000/api/v1/women/?page_size=4 Здесь дополнительно прописан параметр page_size со значением 4. Теперь у нас на странице будет по четыре записи. Этот параметр можно использовать, если клиент пожелает изменить число выдаваемых записей на странице. Но их будет не более max_page_size. Например, если этот параметр установить в два:
max_page_size = 2
Пагинация¶
Django предоставляет высокоуровневые и низкоуровневые способы управления постраничными данными — то есть данными, разделенными на несколько страниц, со ссылками «Предыдущая/Следующая».
Класс Paginator ¶
Под капотом все методы пагинации используют класс Paginator . Он выполняет всю тяжелую работу по разбиению QuerySet на Page объекты.
Пример¶
Дайте Paginator список объектов, плюс количество элементов, которые вы хотели бы иметь на каждой странице, и он предоставит вам методы для доступа к элементам для каждой страницы:
>>> from django.core.paginator import Paginator >>> objects = ["john", "paul", "george", "ringo"] >>> p = Paginator(objects, 2) >>> p.count 4 >>> p.num_pages 2 >>> type(p.page_range) >>> p.page_range range(1, 3) >>> page1 = p.page(1) >>> page1 >>> page1.object_list ['john', 'paul'] >>> page2 = p.page(2) >>> page2.object_list ['george', 'ringo'] >>> page2.has_next() False >>> page2.has_previous() True >>> page2.has_other_pages() True >>> page2.next_page_number() Traceback (most recent call last): . EmptyPage: That page contains no results >>> page2.previous_page_number() 1 >>> page2.start_index() # The 1-based index of the first item on this page 3 >>> page2.end_index() # The 1-based index of the last item on this page 4 >>> p.page(0) Traceback (most recent call last): . EmptyPage: That page number is less than 1 >>> p.page(3) Traceback (most recent call last): . EmptyPage: That page contains no results
Обратите внимание, что вы можете передать Paginator список/кортеж, Django QuerySet или любой другой объект с методом count() или __len__() . При определении количества объектов, содержащихся в переданном объекте, Paginator сначала попытается вызвать count() , затем вернется к использованию len() , если у переданного объекта нет метода count() . Это позволяет таким объектам, как QuerySet Django, использовать более эффективный count() метод, если он доступен.
Пагинация ListView ¶
django.views.generic.list.ListView предоставляет встроенный способ постраничного отображения списка. Вы можете сделать это, добавив атрибут paginate_by к вашему классу представления, например:
from django.views.generic import ListView from myapp.models import Contact class ContactListView(ListView): paginate_by = 2 model = Contact
Это ограничивает количество объектов на странице и добавляет paginator и page_obj к context . Чтобы пользователи могли перемещаться между страницами, добавьте ссылки на следующую и предыдущую страницу в ваш шаблон следующим образом:
for contact in page_obj %> contact.full_name|upper >>br> . endfor %> div class="pagination"> span class="step-links"> if page_obj.has_previous %> a href="?page=1">« firsta> a href="?page= page_obj.previous_page_number >>">previousa> endif %> span class="current"> Page page_obj.number >> of page_obj.paginator.num_pages >>. span> if page_obj.has_next %> a href="?page= page_obj.next_page_number >>">nexta> a href="?page= page_obj.paginator.num_pages >>">last »a> endif %> span> div>
Использование Paginator в функции представления¶
Вот пример использования Paginator в функции представления для постраничного просмотра набора запросов:
from django.core.paginator import Paginator from django.shortcuts import render from myapp.models import Contact def listing(request): contact_list = Contact.objects.all() paginator = Paginator(contact_list, 25) # Show 25 contacts per page. page_number = request.GET.get("page") page_obj = paginator.get_page(page_number) return render(request, "list.html", "page_obj": page_obj>)
В шаблоне list.html можно включить навигацию между страницами таким же образом, как и в шаблоне для ListView выше.
Pagination
Pagination – это компонент, используемый для загрузки данных по страницам. Это позволяет пользователям осуществлять навигацию по большому объему данных с предсказуемой производительностью.
См. также компонент SimplePagination, который имеет другое визуальное представление и может использоваться внутри Table , DataGrid и других компонентов-списков.
XML-имя компонента: pagination .
Основы
Pagination содержит номера страниц, что позволяет пользователю выбрать определенную страницу, и выпадающий список для выбора количества элементов на странице.
Чтобы использовать разбивку на страницы вместе с компонентом-списком, необходимо привязать компонент Pagination и компонент-список к одному и тому же источнику данных.
Привязка к данным
Чтобы создать Pagination , связанный с данными, используйте вложенный элемент containerProvider или loaderProvider .
У Pagination должен быть только один провайдер.
containerProvider
(1) (2)
1 | CollectionContainer для сущности Customer . |
2 | Компонент Pagination связан с источником данных с помощью атрибута dataContainer вложенного элемента containerProvider . |
loaderProvider
(1) (2)
1 | citiesDl CollectionLoader загружает коллекцию сущностей по JPQL-запросу. |
2 | Компонент Pagination связан с источником данных с помощью атрибута loaderId вложенного элемента loaderProvider . |
Количество элементов на странице
У Pagination есть специальный ComboBox со списком параметров для ограничения количества элементов на одной странице. Чтобы сделать его видимым, установите для атрибута itemsPerPageVisible значение true . Значение по умолчанию – false .
Значение по умолчанию для этого списка указано в свойстве jmix.ui.component.pagination-items-per-page-options.
Можете настроить свой список параметров, используя атрибут itemsPerPageOptions . Значением атрибута должен быть список параметров, разделенных запятыми:
Значения, меньшие или равные 0 , игнорируются. Значения, превышающие максимальное количество загружаемых записей заменяются на данное максимальное значение.
Используйте атрибут itemsPerPageDefaultValue чтобы задать значение по умолчанию из списка параметров:
Атрибут itemsPerPageUnlimitedOptionVisible задает видимость неограниченного (null) значения параметра в списке ComboBox . Значение по умолчанию – true .
Если выбран параметр null , компонент попытается загрузить все данные которые возможно при текущем ограничении на максимальное количество загружаемых записей.
Максимальное количество загружаемых записей для всех сущностей определено с помощью свойства jmix.ui.default-max-fetch-size. Значение по умолчанию для этого свойства — 10000 . У конкретной сущности может быть другое значение максимального количество загружаемых записей, заданное с помощью свойства jmix.ui.entity-max-fetch-size.
Количество видимых страниц
Компонент Pagination позволяет изменять количество максимально видимых страниц с помощью атрибута maxVisiblePages . Страниц в компонент может быть много, но пользователи будут видеть сразу несколько страниц, в соответствии с атрибутом maxVisiblePages . Значение по умолчанию – 5 . Например, если установить maxVisiblePages=»3″ , будет видно только три страницы одновременно:
События и слушатели
Чтобы сгенерировать заглушку слушателя в Jmix Studio, выберите компонент в XML-дескрипторе экрана или на панели иерархии Jmix UI и используйте вкладку Handlers на панели инспектора Jmix UI.
В качестве альтернативы вы можете воспользоваться кнопкой Generate Handler на верхней панели контроллера экрана.
PageChangeEvent
PageChangeEvent отправляется, когда пользователь выбирает другую страницу или нажимает на кнопки навигации (следующая, предыдущая и т.д.).
Пример подписки на событие компонента Pagination , объявленного в XML с идентификатором pagination id:
@Subscribe("pagination") public void onPaginationPageChange(Pagination.PageChangeEvent event)
Чтобы создать слушателя события программно, используйте метод компонента addPageChangeListener() .
BeforeRefreshEvent
BeforeRefreshEvent отправляется перед обновлением данных, когда пользователь нажимает «следующая», «предыдущая» и т.д. Можно предотвратить обновление контейнера данных, вызвав метод preventRefresh() , например:
@Subscribe("paginationWithDefault") public void onPaginationWithDefaultBeforeRefresh(PaginationComponent.BeforeRefreshEvent event) < if (event.getSource().getDataBinder().getCount() >10) (1) event.preventRefresh(); (2) >
1 | Проверьте количество экземпляров в хранилище данных. |
2 | Предотвратите обновление данных. |
Чтобы создать слушателя события программно, используйте метод компонента addBeforeRefreshListener() .
AfterRefreshEvent
AfterRefreshEvent вызывается при обновлении данных.
Пример подписки на событие компонента Pagination , объявленного в XML с идентификатором paginationWithDefault :
@Subscribe("paginationWithDefault") public void onPaginationWithDefaultAfterRefresh(PaginationComponent.AfterRefreshEvent event)
Чтобы создать слушателя события программно, используйте метод компонента addAfterRefreshListener() .
TotalCountDelegate
TotalCountDelegate – это слушатель, который используется для получения общего количества элементов. Например:
@Install(to = "pagination", subject = "totalCountDelegate") private Integer paginationTotalCountDelegate()
Чтобы создать слушателя TotalCountDelegate программно, используйте метод setTotalCountDelegate() .
Все XML-атрибуты
Просматривать и редактировать атрибуты, применимые к компоненту, можно с помощью панели инспектора Jmix UI в конструкторе экранов Studio.
This page was built using the Antora default UI.
The source code for this UI is licensed under the terms of the MPL-2.0 license.
Пагинация ¶
Django предоставляет высокоуровневые и низкоуровневые способы управления данными, разбитыми на страницы, то есть данными, которые разделены на несколько страниц со ссылками «Предыдущий / Следующий».
Paginator Класс ¶
Под капотом все методы разбивки на страницы используют этот Paginator класс. Он делает всю тяжелую работу по фактическому разбиению QuerySet на Page объекты.
Пример ¶
Дайте Paginator список объектов, а также количество элементов, которые вы хотите разместить на каждой странице, и получите методы для доступа к элементам для каждой страницы:
>>> from django.core.paginator import Paginator >>> objects = ['john', 'paul', 'george', 'ringo'] >>> p = Paginator(objects, 2) >>> p.count 4 >>> p.num_pages 2 >>> type(p.page_range) >>> p.page_range range(1, 3) >>> page1 = p.page(1) >>> page1 >>> page1.object_list ['john', 'paul'] >>> page2 = p.page(2) >>> page2.object_list ['george', 'ringo'] >>> page2.has_next() False >>> page2.has_previous() True >>> page2.has_other_pages() True >>> page2.next_page_number() Traceback (most recent call last): . EmptyPage: That page contains no results >>> page2.previous_page_number() 1 >>> page2.start_index() # The 1-based index of the first item on this page 3 >>> page2.end_index() # The 1-based index of the last item on this page 4 >>> p.page(0) Traceback (most recent call last): . EmptyPage: That page number is less than 1 >>> p.page(3) Traceback (most recent call last): . EmptyPage: That page contains no results
Обратите внимание, что вы можете Paginator указать список / кортеж, Django QuerySet или любой другой объект с помощью метода count() или __len__() . При определении количества объектов, содержащихся в переданном объекте, Paginator сначала будет попытаться вызвать count() , а затем вернуться к использованию, len() если переданный объект не имеет count() метода. Это позволяет таким объектам, как Django, QuerySet использовать более эффективный count() метод, когда он доступен.
Пагинация ListView ¶
django.views.generic.list.ListView предоставляет встроенный способ разбивки отображаемого списка на страницы. Вы можете сделать это, добавив paginate_by атрибут в свой класс представления, например:
from django.views.generic import ListView from myapp.models import Contact class ContactListView(ListView): paginate_by = 2 model = Contact
Это ограничивает количество объектов на странице и добавляет paginator и page_obj к context . Чтобы пользователи могли перемещаться между страницами, добавьте в свой шаблон ссылки на следующую и предыдущую страницу следующим образом:
for contact in page_obj %> contact.full_name|upper >>br> . endfor %> div class="pagination"> span class="step-links"> if page_obj.has_previous %> a href="?page=1">« firsta> a href="?page= page_obj.previous_page_number >>">previousa> endif %> span class="current"> Page page_obj.number >> of page_obj.paginator.num_pages >>. span> if page_obj.has_next %> a href="?page= page_obj.next_page_number >>">nexta> a href="?page= page_obj.paginator.num_pages >>">last »a> endif %> span> div>
Использование Paginator в функции просмотра ¶
Вот пример использования Paginator функции просмотра для разбивки набора запросов на страницы:
from django.core.paginator import Paginator from django.shortcuts import render from myapp.models import Contact def listing(request): contact_list = Contact.objects.all() paginator = Paginator(contact_list, 25) # Show 25 contacts per page. page_number = request.GET.get('page') page_obj = paginator.get_page(page_number) return render(request, 'list.html', 'page_obj': page_obj>)
В шаблоне list.html вы можете включить навигацию между страницами так же, как в шаблоне для ListView вышеупомянутого.