Filtrar un modelo por un campo utilizando un formulario

Introducción

Con Django podemos crear filtros para nuestros modelos de forma relativamente sencilla jugando con los campos de un formulario y capturándolos en la vista para aplicarlos en las queryset.

Supongamos el siguiente proyecto donde tenemos un modelo Persona muy simple:

models.py

from django.db import models

class Persona(models.Model):
  nombre = models.CharField(max_length=100)
  edad = models.SmallIntegerField()

Añadimos varias personas a través de nuestro panel de administración:

Listando las instancias

Para devolver una lista de nuestras personas utilizaremos una consulta básica al modelo que devuelva todas sus instancias:

views.py

from django.shortcuts import render
from .models import *


def home(request):
    personas = Persona.objects.all()

    return render(request, "core/home.html", {'personas': personas})

El template que visualizaría el contenido contendría sin mucha complicación un bucle for para recorrer las personas y mostrarlas:

home.html

<body>
    <h2>Lista de personas</h2>

    <ul>
    {% for persona in personas %}
    	<li>{{persona.nombre}}, {{persona.edad}} años</li>
    {% endfor %}
    </ul>
</body>

Este sería el resultado:

Filtrando las instancias

Ahora viene lo interesante, ¿cómo podemos añadir un filtro para mostrar sólo las personas que tengan una edad mínima?

Lo primero sería añadir un formulario que valide contra la propia vista un campo con la edad mínima:

home.html

<body>
    <h2>Lista de personas</h2>

    <ul>
    {% for persona in personas %}
    	<li>{{persona.nombre}}, {{persona.edad}} años</li>
    {% endfor %}
    </ul>

    <form action="/" method="POST">
        Edad mínima: 
        <input type="number" name="edad" value="0" style="width:40px" / >
        <input type="submit" value="Filtrar">
        {% csrf_token %}
    </form>
</body>

Quedaría así:

Lo único a comentar sería el uso obligatorio del token csrf entre los tags form para proteger el formulario de las peticiones entre sitios cruzados.

Para procesar el campo con el name="edad", buscaríamos ese campo en el diccionario POST de la petición y lo transformaríamos a número entero para poder utilizarlo en el filtro del queryset:

if request.POST.get('edad'):
  edad = int(request.POST.get('edad'))

El código final de la vista, una vez aplicado el filter quedaría de esta forma:

views.py

from django.shortcuts import render
from .models import *


def home(request):
    personas = Persona.objects.all()
    edad = 0  # Filtro por defecto

    if request.POST.get('edad'):
        edad = int(request.POST.get('edad'))
        personas = personas.filter(edad__gte=edad)

    return render(request, "core/home.html", {'personas': personas, 'edad':edad})

Fijaros como envío el propio campo edad de nuevo al template, así podríamos mostrarlo como valor del input:

<input type="number" name="edad" value="{{edad}}" style="width:40px" / >

Con esto ya tendríamos nuestro sistema de filtrado para la queryset original con todas las personas aplicándole el filter sólo en caso de recibir el parámetro con la edad por POST:

 

Django 2.2
08/06/2019

Recursos disponibles

attach_file
Código del tutorial