¿Cómo realizo el filtrado de consultas en las plantillas de django?

82

Necesito realizar una consulta filtrada desde dentro de una plantilla de django, para obtener un conjunto de objetos equivalente al código de Python dentro de una vista:

queryset = Modelclass.objects.filter(somekey=foo)

En mi plantilla me gustaría hacer

{% for object in data.somekey_set.FILTER %}

pero parece que no puedo averiguar cómo escribir FILTER.

Ber
fuente

Respuestas:

119

No puedes hacer esto, que es por diseño. Los autores del marco de Django pretendían una separación estricta del código de presentación de la lógica de datos. El filtrado de modelos es lógica de datos y la salida de HTML es lógica de presentación.

Entonces tienes varias opciones. Lo más fácil es filtrar y luego pasar el resultado a render_to_response. O puede escribir un método en su modelo para poder decir {% for object in data.filtered_set %}. Finalmente, podría escribir su propia etiqueta de plantilla, aunque en este caso específico no lo desaconsejaría.

Eli Courtwright
fuente
2
¡Hola gente es 2014 ahora! Aproximadamente 6 años después, las bibliotecas JS han hecho un gran progreso, y el filtrado de una cantidad no extremadamente grande de datos debería realizarse en el lado del cliente con el soporte de alguna buena biblioteca de scripts java, o al menos AJAX-ed.
andilabs
1
@andi: Ciertamente estoy de acuerdo con conjuntos de datos incluso moderadamente grandes, por ejemplo, incluso miles de filas en una tabla. Después de haber trabajado en bases de datos con millones de filas, definitivamente todavía hay un lugar para el filtrado del lado del servidor :)
Eli Courtwright
claro, pero solo quería señalar que las personas que a menudo tratan con pocos K de filas, que la experiencia de interacción agradable para el usuario puede ocurrir en su navegador. Y para las personas que manejan grandes conjuntos de datos, algún enfoque híbrido puede ser una buena solución, por ejemplo, filtrar en términos de pocos M a pocos K en el lado del servidor, y otro personal más liviano dentro de estos pocos K en el lado del cliente.
andilabs
9
@andi Excepto en situaciones en las que está filtrando contenido en función de permisos que nunca se realizarían del lado del cliente. ¿Derecho?
38

Solo agrego una etiqueta de plantilla adicional como esta:

@register.filter
def in_category(things, category):
    return things.filter(category=category)

Entonces puedo hacer:

{% for category in categories %}
  {% for thing in things|in_category:category %}
    {{ thing }}
  {% endfor %}
{% endfor %}
tobych
fuente
Estoy tratando esta solución pero sigo desencadenar un error: 'for' statements should use the format 'for x in y': for p in r | people_in_roll_department:d. ¿Algunas ideas?
Diosney
12

Me encuentro con este problema de forma regular y, a menudo, utilizo la solución "agregar un método". Sin embargo, definitivamente hay casos en los que "agregar un método" o "calcularlo en la vista" no funcionan (o no funcionan bien). Por ejemplo, cuando está almacenando en caché fragmentos de plantilla y necesita algún cálculo de base de datos no trivial para producirlo. No desea hacer el trabajo de la base de datos a menos que sea necesario, pero no sabrá si es necesario hasta que esté profundamente en la lógica de la plantilla.

Algunas otras posibles soluciones:

  1. Utilice la etiqueta de plantilla {% expr <expression> como <var_name>%} que se encuentra en http://www.djangosnippets.org/snippets/9/ La expresión es cualquier expresión legal de Python con el contexto de su plantilla como su ámbito local.

  2. Cambia tu procesador de plantillas. Jinja2 ( http://jinja.pocoo.org/2/ ) tiene una sintaxis que es casi idéntica a la del lenguaje de plantilla de Django, pero con toda la potencia de Python disponible. También es más rápido. Esto se puede hacer al por mayor, o podría limitar su uso a las plantillas que se está trabajando, pero el uso de plantillas "más seguros" de Django para las páginas de diseño-mantenido.

Peter Rowell
fuente
9

La otra opción es que si tiene un filtro que siempre desea aplicar, agregue un administrador personalizado en el modelo en cuestión que siempre aplica el filtro a los resultados devueltos.

Un buen ejemplo de esto es un Eventmodelo, donde para el 90% de las consultas que haces en el modelo vas a querer algo como Event.objects.filter(date__gte=now), es decir, normalmente estás interesado en Eventslas próximas. Esto se vería así:

class EventManager(models.Manager):
    def get_query_set(self):
        now = datetime.now()
        return super(EventManager,self).get_query_set().filter(date__gte=now)

Y en el modelo:

class Event(models.Model):
    ...
    objects = EventManager()

Pero, de nuevo, esto aplica el mismo filtro a todas las consultas predeterminadas realizadas en el Eventmodelo y, por lo tanto, algunas de las técnicas descritas anteriormente no son tan flexibles.

mrmagooey
fuente
9

Esto se puede solucionar con una etiqueta de asignación:

from django import template

register = template.Library()

@register.assignment_tag
def query(qs, **kwargs):
    """ template tag which allows queryset filtering. Usage:
          {% query books author=author as mybooks %}
          {% for book in mybooks %}
            ...
          {% endfor %}
    """
    return qs.filter(**kwargs)
Chrisv
fuente
3
assign_tag se ha eliminado en Django 2.0
Andreas Bergström
1

Para cualquiera que busque una respuesta en 2020. Esto funcionó para mí.

En Vistas:

 class InstancesView(generic.ListView):
        model = AlarmInstance
        context_object_name = 'settings_context'
        queryset = Group.objects.all()
        template_name = 'insta_list.html'

        @register.filter
        def filter_unknown(self, aVal):
            result = aVal.filter(is_known=False)
            return result

        @register.filter
        def filter_known(self, aVal):
            result = aVal.filter(is_known=True)
            return result

En plantilla:

{% for instance in alarm.qar_alarm_instances|filter_unknown:alarm.qar_alarm_instances %}

En pseudocódigo:

For each in model.child_object|view_filter:filter_arg

Espero que ayude.

Krzysztof Szumko
fuente