33

I want to look for a certain string in several fields of a Model in Django. Ideally, it would go something similar to:

keyword = 'keyword'
fields = ['foo', 'bar', 'baz']
results = []
for field in fields:
    lookup = "%s__contains"
    results.append(Item.objects.filter(lookup=keyword))

Of course this won't work, as "lookup" can't be resolved into a field. Is there any other way to do this?

Yuval Adam
  • 161,610
  • 92
  • 305
  • 395
Berislav Lopac
  • 16,656
  • 6
  • 71
  • 80

3 Answers3

60

I would prefer to use the Q object for something like this.

from django.db.models import Q

keyword = 'keyword'
fields = ['foo', 'bar', 'baz']

Qr = None
for field in fields:
    q = Q(**{"%s__contains" % field: keyword })
    if Qr:
        Qr = Qr | q # or & for filtering
    else:
        Qr = q

# this you can now combine with other filters, exclude etc.    
results = MyModel.objects.filter(Qr)
Botond Béres
  • 16,057
  • 2
  • 37
  • 50
  • 2
    This is an absolutely wonderful answer. It made my day to come across it. Have you posted about this anywhere? I don't think that most people realize that this is possible (looping over field names to populate a dict of Q object kwargs). – jMyles Jul 20 '11 at 20:25
  • 1
    @jMyles: Thanks, glad I could help. No, I don't think I've posted about it elsewhere. But in general combining `Q` objects in a dynamic way, is a very powerful method to build complex queries easily. – Botond Béres Jul 21 '11 at 11:17
  • I have just posted a question to which the answer might be similar - I may use a loop and Q's AND operator. It's here: http://stackoverflow.com/questions/6783747/django-queryset-to-match-all-related-objects – jMyles Jul 21 '11 at 22:51
  • 1
    It's 2017 and this little thing still helped me so much. Thanks bro – jinchuika Mar 16 '17 at 21:53
  • How would this work if I want to do a dyanamic field name with __isnull? This example is creating a dict. I'm not sure what to put for the value instead of keyword. – JoeMjr2 Mar 12 '20 at 21:16
  • 1
    it's 2022 and it still works perfectly in Django 4.0.4 – Krishnadas PC Jul 20 '22 at 05:53
39

I think there may be a better way to do this with the Django query system. Here's how to do it your way.

Python allows you to pass dictionaries to be used as argument lists by prefixing them with **. With a spot of luck, you should be able to do something like this:

lookup = "%s__contains" % field
results.append(Item.objects.filter(**{ lookup: keyword}))
Dial Z
  • 675
  • 6
  • 8
  • Have a look at Botondus's answer below. It's the solution with the Django query system that I referred to, and it should hopefully run faster since it'll execute in SQL. – Dial Z Aug 13 '09 at 17:23
7

I like DialZ's answer but for performance reasons you should build the query and then hit the database once instead of concatenating all the results into a list:

keyword = 'keyword'
fields = ['foo', 'bar', 'baz']

# this makes an empty queryset object which we can
# add to later using the | operator
results = Item.objects.none()

for field in fields:
    lookup = "%s__contains" % field
    query = {lookup : keyword}
    results = results | Item.objects.filter(**query)

I havn't done one of these in a while, but I'm pretty sure django will not actually hit the database at all in this code. It will only perform a query when you access the data contained in the records

Jiaaro
  • 74,485
  • 42
  • 169
  • 190