Paginação no Django

django-7_abertura

Seguindo com o desenvolvimento do “Agenda de Eventos” para, tal qual foi feito na “Agenda Mequetrefe”, implementar a paginação na opção de listagem de todos os eventos registrados. No Django esta tarefa é implementada através das classes do módulo Paginator.

Visão

O Paginator já faz parte do Django e para incluí-lo à visão basta acrescentá-lo ao começo do arquivo “./events/views.py” — explicarei a função o módulo InvalidPage daqui a pouco.

from django.core.paginator import Paginator, InvalidPage

Claro, não é obrigatório, mas é interessante definir uma variável especificando a quantidade de itens a serem exibidos por página e assim evitar o uso de números mágicos dentro do código.

...
ITEMS_PER_PAGE = 5
...

Defini como cinco o número de itens por página mas o valor pode ser qualquer outro, apenas não queria ficar incluindo novos eventos desnecessariamente… 🙂

Tratando a paginação de itens

Na função que exibe todos os eventos cadastrados, a all(), a primeira coisa a ser fazer é recuperar o número da página que deverá ser exibida.

page = request.GET.get('page', 1)

Neste caso, recupera-se da requisição HTTP, no método GET, o valor de ‘page’ e caso ela não exista por lá, vá à primeira página.

A classe Paginator é instanciada com dois parâmetros, o primeiro deles é a QuerySet para o acesso aos dados do modelo e o outro é justamente a quantidade de itens a exibir em cada página (definido em “ITEMS_PER_PAGE”).

paginator = Paginator(Event.objects.all(), ITEMS_PER_PAGE)
total = paginator.count

A variável “total” guardará a soma de todos os elementos dentro de Event para ser exibido no gabarito — como a listagem é paginada o “{{ event|length }}” não retornará mais a quantidade exata de eventos.

Assim usa-se o Paginator para recuperar do modelo somente os itens correspondentes de uma determinada página.

try:
    events = paginator.page(page)
except InvalidPage:
    events = paginator.page(1)

Inicialmente “tenta-se” ir para a página solicitada via GET, sendo ela um número válido a variável “events” conterá a lista dos eventos correspondentes àquela página em um objeto da classe Page.

Porém, se o número informado for inválido, produzirá um erro de InvalidPage, o erro será capturado e a primeira página será exibida — dentro de Paginator há ainda as classes PageNotAnInteger e também a PageEmpty , que permitem, respectivamente, tratar números de página não inteiros e páginas válidas que não retornam registros.

No final a função ficará assim:

def all(request):
    page = request.GET.get('page', 1)
    paginator = Paginator(Event.objects.all(), ITEMS_PER_PAGE)
    total = paginator.count

    try:
        events = paginator.page(page)
    except InvalidPage:
        events = paginator.page(1)

    context = {
        'events': events,
            'total': total,
            'priorities': Event.priorities_list,
            'today': localdate(),
    }
    return render(request, 'events.html', context)

E neste ponto já é possível listar todos os eventos acrescentando “?page=«página»” diretamente na URL.

Gabarito

Para deixar a paginação mais funcional é preciso incluir elementos que permitam a navegação entre as páginas por parte do usuário. Dentro do gabarito “./events/template/events.html” foi acrescentado logo após o fechamento da tabela o seguinte:

{% with events as paginated %}
    {% include "pagination.html" %}
{% endwith %}

Em “with”/”endwith” é possível definir uma nova variável dentro do gabarito, ela pode ter uma valor específico ou o valor de uma variável já existente. E neste caso “paginated” recebe o conteúdo de “events” antes do gabarito “pagination.html” ser incluído.

Motivo? Não é exigência do Paginator, ele funcionará sem isto, mas desta forma é possível reutilizar este gabarito em qualquer outro módulo sem a necessidade de alterações ou até duplicá-lo.

{% if paginated.has_other_pages %}
...
    {% if paginated.has_previous %}
    ...
        ... href="?page={{ paginated.previous_page_number }}" ...
    {% else %}
        ...
    {% endif %}
    {% for page in paginated.paginator.page_range %}
        {% if paginated.number == page %}
        ... {{ page }} ...
        {% else %}
        ... href="?page={{ page }}" ... {{ page }} ...
        {% endif %}
    {% endfor %}
    {% if paginated.has_next %}
        ... href="?page={{ paginated.next_page_number }} ...
    {% else %}
        ...
    {% endif %}
{% endif %}

Aqui são usados alguns atributos específicos da classe Page para a montagem da barra de navegação entre as páginas. A primeira verificação é utiliza o atributo ‘has_other_pages’ para saber se haverá necessidade de exibir a paginação e os demais são os seguinte:

  • ‘has_previous’ : Falso ou verdadeiro para o caso de existir uma página anterior;
  • ‘previous_page_number’ : O número da página anterior, caso exista;
  • ‘paginator.page_range’ : Na verdade é atributo da Paginator e não da Page mas está acessível e contém a lista de páginas disponíveis;
  • ‘number’ : A página atual;
  • ‘has_next’ : Falso ou verdadeiro para o caso de existir uma próxima página e
  • ‘next_page_number’ : O número da próxima página, caso exista.

As partes do HTML são providos pela classe Pagination da versão 4 do Bootstrap.

Modelo

Aproveitei para fazer um pequeno ajuste no modelo criando dentro dele a classe Meta:

class Event(models.Model):
    ...
    class Meta:
        ordering = ('-date', '-priority', 'event',)
    ...

E definido nela a ordenação padrão para a exibição dos dados deste modelo.

Finalizando…

django-7_segunda_pagina

E pronto! A listagem de todos os eventos registrados passa a ser paginada — cinco eventos por página mas é facilmente configurável para exibir mais — e aplicando a mesma lógica é possível fazer o mesmo com as demais listas dentro da aplicação.

E só relembrando que o programa está disponível no GitHub!

Anúncios