Código del componente de paginación
class PaginadorPersonalizado: def init(self, pagina_actual, total_registros, ruta_base, query_params, items_por_pagina=8, max_paginas_visibles=11): """ Inicializa los datos de paginación :param pagina_actual: número de página solicitada :param total_registros: cantidad total de registros en la base de datos :param ruta_base: prefijo de URL para los enlaces :param query_params: objeto request.GET :param items_por_pagina: cantidad de elementos por página :param max_paginas_visibles: número máximo de botones de página visibles """ try: pagina_actual = int(pagina_actual) except (TypeError, ValueError): pagina_actual = 1
if pagina_actual < 1:
pagina_actual = 1
self.pagina_actual = pagina_actual
self.total_registros = total_registros
self.items_por_pagina = items_por_pagina
self.ruta_base = ruta_base
total_paginas, resto = divmod(total_registros, items_por_pagina)
if resto:
total_paginas += 1
self.total_paginas = total_paginas
self.max_paginas_visibles = max_paginas_visibles
self.mitad = max_paginas_visibles // 2
import copy
params_copiados = copy.deepcopy(query_params)
params_copiados._mutable = True
self.params = params_copiados
@property
def indice_inicio(self):
return (self.pagina_actual - 1) * self.items_por_pagina
@property
def indice_fin(self):
return self.pagina_actual * self.items_por_pagina
def _calcular_rango(self):
if self.total_paginas <= self.max_paginas_visibles:
return 1, self.total_paginas + 1
if self.pagina_actual <= self.mitad:
return 1, self.max_paginas_visibles + 1
if (self.pagina_actual + self.mitad) > self.total_paginas:
return self.total_paginas - self.max_paginas_visibles + 1, self.total_paginas + 1
return self.pagina_actual - self.mitad, self.pagina_actual + self.mitad + 1
def generar_html(self):
inicio, fin = self._calcular_rango()
elementos = []
# Botón primera página
self.params['page'] = 1
elementos.append('<li><a href="%s?%s">Primera</a></li>' % (self.ruta_base, self.params.urlencode()))
# Botón página anterior
if self.pagina_actual <= 1:
elementos.append('<li class="disabled"><a href="#">Anterior</a></li>')
else:
self.params['page'] = self.pagina_actual - 1
elementos.append('<li><a href="%s?%s">Anterior</a></li>' % (self.ruta_base, self.params.urlencode()))
# Botones numéricos
for num in range(inicio, fin):
self.params['page'] = num
clase = ' class="active"' if num == self.pagina_actual else ''
elementos.append('<li><a href="%s?%s">%s</a></li>' % (clase, self.ruta_base, self.params.urlencode(), num))
# Botón página siguiente
if self.pagina_actual >= self.total_paginas:
elementos.append('<li class="disabled"><a href="#">Siguiente</a></li>')
else:
self.params['page'] = self.pagina_actual + 1
elementos.append('<li><a href="%s?%s">Siguiente</a></li>' % (self.ruta_base, self.params.urlencode()))
# Botón última página
self.params['page'] = self.total_paginas
elementos.append('<li><a href="%s?%s">Última</a></li>' % (self.ruta_base, self.params.urlencode()))
return ''.join(elementos)
paginador.py
</div>Uso desde la vista
------------------
<div>```
from django.shortcuts import render
from .models import Libro
from .utils.paginador import PaginadorPersonalizado
def listar_libros(request):
pagina = request.GET.get('page', 1)
total = Libro.objects.count()
ruta = request.path
paginador = PaginadorPersonalizado(
pagina_actual=pagina,
total_registros=total,
ruta_base=ruta,
query_params=request.GET,
items_por_pagina=8,
max_paginas_visibles=11
)
libros = Libro.objects.all()[paginador.indice_inicio:paginador.indice_fin]
return render(request, 'listar_libros.html', {'libros': libros, 'paginador': paginador})
vistas.py
listar\_libros.html
</div>El componente copia el objeto `request.GET` utilizando `deepcopy` y lo marca como mutable, lo que permite preservar los parámetros de búsqueda existentes mientras se navega entre páginas. Cada enlace generado incluye automáticamente todos los query strings previos junto con el parámetro `page` actualizado.
</div></body></html>