Paginação

Paginação é o processo de dividir uma grande lista de itens (exemplo: uma lista de receitas, posts, produtos, etc.) em várias páginas menores, para que o usuário visualize apenas um pedaço da lista por vez.

O Django já vem com uma ferramenta nativa para isso: django.core.paginator

Os principais elementos são:
Componente O que faz
Paginator(queryset, n) Divide o queryset em páginas com até n itens
page_obj = paginator.get_page(page_number) Pega a página específica
page_obj.object_list Os itens dessa página (ex: 6 receitas)
page_obj.has_next() Verifica se existe uma próxima página
page_obj.has_previous() Verifica se existe uma página anterior
paginator.num_pages Total de páginas

| Paginação Django

pagination.py
import math


def make_pagination_range(
    page_range,
    qty_pages,
    current_page,
):
    middle_range = math.ceil(qty_pages / 2)
    start_range = current_page - middle_range
    stop_range = current_page + middle_range
    total_pages = len(page_range)

    start_range_offset = abs(start_range) if start_range < 0 else 0

    if start_range < 0:
        start_range = 0
        stop_range += start_range_offset

    if stop_range >= total_pages:
        start_range = start_range - abs(total_pages - stop_range)

    pagination = page_range[start_range:stop_range]
    return {
        'pagination': pagination,
        'page_range': page_range,
        'qty_pages': qty_pages,
        'current_page': current_page,
        'total_pages': total_pages,
        'start_range': start_range,
        'stop_range': stop_range,
        'first_page_out_of_range': current_page > middle_range,
        'last_page_out_of_range': stop_range < total_pages,
    }
views_pagination.py
from django.core.paginator import Paginator
from django.shortcuts import render
from .models import Recipe
from .utils.pagination import make_pagination_range  # ajuste o import conforme sua estrutura


def recipe_list_view(request):
    recipes = Recipe.objects.all().order_by('-id')  # Pegando todas as receitas
    page_number = request.GET.get('page', 1)        # Pegando o número da página via query string ?page=

    paginator = Paginator(recipes, 6)  # Exemplo: 6 receitas por página
    page_obj = paginator.get_page(page_number)      # Pega o objeto da página atual

    pagination_range = make_pagination_range(
        page_range=paginator.page_range,  # Exemplo: range(1, total_pages+1)
        qty_pages=5,                      # Quantidade máxima de botões de página que quer exibir (ex: 5 botões)
        current_page=page_obj.number,     # Página atual
    )

    context = {
        'recipes': page_obj.object_list,         # Lista de receitas desta página
        'page_obj': page_obj,                    # Objeto da página (tem métodos úteis como has_next, has_previous)
        'pagination_range': pagination_range,    # Range customizado usando sua função
    }

    return render(request, 'recipes/recipe_list.html', context)
recipe_list.html
<h1>Lista de Receitas</h1>

<ul>
  {% for recipe in recipes %}
    <li>{{ recipe.title }}</li>
  {% endfor %}
</ul>

<div class="pagination">
  {% if page_obj.has_previous %}
    <a href="?page=1">Primeira</a>
    <a href="?page={{ page_obj.previous_page_number }}">Anterior</a>
  {% endif %}

  {% for page in pagination_range.pagination %}
    {% if page == page_obj.number %}
      <strong>{{ page }}</strong>
    {% else %}
      <a href="?page={{ page }}">{{ page }}</a>
    {% endif %}
  {% endfor %}

  {% if page_obj.has_next %}
    <a href="?page={{ page_obj.next_page_number }}">Próxima</a>
    <a href="?page={{ page_obj.paginator.num_pages }}">Última</a>
  {% endif %}
</div>