How do I perform query filtering in django templates
Asked Answered
A

8

104

I need to perform a filtered query from within a django template, to get a set of objects equivalent to python code within a view:

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

In my template I would like to do

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

but I just can't seem to find out how to write FILTER.

Award answered 21/10, 2008 at 23:55 Comment(0)
H
143

You can't do this, which is by design. The Django framework authors intended a strict separation of presentation code from data logic. Filtering models is data logic, and outputting HTML is presentation logic.

So you have several options. The easiest is to do the filtering, then pass the result to render_to_response. Or you could write a method in your model so that you can say {% for object in data.filtered_set %}. Finally, you could write your own template tag, although in this specific case I would advise against that.

Hynes answered 22/10, 2008 at 0:0 Comment(5)
Hello People is 2014 now! About 6 year later JS libraries has made huge progress, and filtering of not extremly big amount of data should be rather done on client side with support of some nice java script library, or at least AJAX-ed.Drupe
@andi: I certainly agree for even moderately large data sets, e.g. even thousands of rows in a table. Having worked on databases with millions of rows, there's still definitely a place for server-side filtering :)Hynes
sure but I just wanted to point all that people dealing with often few K of rows, thath nice interaction expirience for user can happen in it's browser. And for people even dealing with huge data sets some hybrid aproach can be a good solution, e.g filter in terms of few M to few K on server side, and other lighter staff inside this few K do on client side.Drupe
@andi Except for situations where you are filtering content based on permissions which would never be done client side. Right?Parthen
Server side filtering is the common and safe case, while sometimes client side filtering can be occasionally opted to avoid redundant calls to server to filter the same limited number of records multiple times. I don't know why some JS library worshippers prefer to do a lot in the client side. I've seen some js based dashboards which are incredibly good at draining your laptop battery fastMilkwhite
A
54

I just add an extra template tag like this:

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

Then I can do:

{% for category in categories %}
  {% for thing in things|in_category:category %}
    {{ thing }}
  {% endfor %}
{% endfor %}
Angieangil answered 7/5, 2013 at 21:41 Comment(2)
I'm trying this solution but it keep triggering an error: 'for' statements should use the format 'for x in y': for p in r | people_in_roll_department:d. Any ideas?Seibold
@diosney you probably to add ".all" in the things sentence. It should be "things.all"Ileostomy
N
15

I run into this problem on a regular basis and often use the "add a method" solution. However, there are definitely cases where "add a method" or "compute it in the view" don't work (or don't work well). E.g. when you are caching template fragments and need some non-trivial DB computation to produce it. You don't want to do the DB work unless you need to, but you won't know if you need to until you are deep in the template logic.

Some other possible solutions:

  1. Use the {% expr <expression> as <var_name> %} template tag found at http://www.djangosnippets.org/snippets/9/ The expression is any legal Python expression with your template's Context as your local scope.

  2. Change your template processor. Jinja2 (https://jinja.palletsprojects.com/) has syntax that is almost identical to the Django template language, but with full Python power available. It's also faster. You can do this wholesale, or you might limit its use to templates that you are working on, but use Django's "safer" templates for designer-maintained pages.

Nolita answered 23/10, 2008 at 17:22 Comment(0)
F
12

The other option is that if you have a filter that you always want applied, to add a custom manager on the model in question which always applies the filter to the results returned.

A good example of this is a Event model, where for 90% of the queries you do on the model you are going to want something like Event.objects.filter(date__gte=now), i.e. you're normally interested in Events that are upcoming. This would look like:

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

And in the model:

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

But again, this applies the same filter against all default queries done on the Event model and so isn't as flexible some of the techniques described above.

Filberto answered 10/9, 2012 at 11:37 Comment(0)
P
12

This can be solved with an assignment tag:

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)

EDIT: assignment_tag was removed in Django 2.0, this will no longer work.

Pawpaw answered 23/12, 2012 at 12:27 Comment(1)
assignment_tag has been removed in Django 2.0Antechoir
P
2

This is my approach:

@register.filter()
def query_filter(value, attr):
    return value.filter(**eval(attr))

In the template:

{{ queryset|query_filter:'{"cod_tipoinmueble":1,"des_operacion": "alquiler"}'|length }}
Pembrook answered 22/11, 2022 at 20:28 Comment(1)
perhaps better json.reads than evil/evalGondola
A
1

For anyone looking for an answer in 2020. This worked for me.

In Views:

 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

In template:

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

In pseudocode:

For each in model.child_object|view_filter:filter_arg

Hope that helps.

Arvid answered 30/4, 2020 at 9:13 Comment(3)
While this would work, it's considered a better practice to create your filters as functions rather than methods on a view class. put them in their own file for better maintainability rather the an losing them in views. Would also allow for better unit testing.Breadwinner
dear @Breadwinner can you please provide some docs or further info over this? looks interesting to me, thanks! How would you call "their own file" in the django environments?Josettejosey
@Josettejosey it's literally in the Django documentation. docs.djangoproject.com/en/4.0/howto/custom-template-tagsBreadwinner
M
1

you can use custom template filter

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

to

{% for object in data.somekey_set|filter_name %}

create filter.py

@register.filter(name='filter_name')
def filter_name(value):
    return value.filter(your rule)
Maggi answered 27/4, 2023 at 0:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.