39

Aim to Achieve:

I want all objects where name attribute contains any word from the list.

I have:

list = ['word1','word2','word3']
ob_list = data.objects.filter( // What to write here ?  )
// or any other way to get the objects where any word in list is contained, in 
// the na-me attribute of data.

For example:

if name="this is word2": Then object with such a name should be returned since word2 is in the list.

Please help!

Peter O.
  • 32,158
  • 14
  • 82
  • 96
Yugal Jindle
  • 44,057
  • 43
  • 129
  • 197

6 Answers6

61

You could use Q objects to constuct a query like this:

from django.db.models import Q

ob_list = data.objects.filter(reduce(lambda x, y: x | y, [Q(name__contains=word) for word in list]))

Edit:

reduce(lambda x, y: x | y, [Q(name__contains=word) for word in list]))

is a fancy way to write

Q(name__contains=list[0]) | Q(name__contains=list[1]) | ... | Q(name__contains=list[-1])

You could also use an explicit for loop to construct the Q object.

Ismail Badawi
  • 36,054
  • 7
  • 85
  • 97
  • can you explaing.. reduce and lambda...? Your solution is working fine. – Yugal Jindle Aug 17 '11 at 05:18
  • 4
    I'm not the one who posted this, but `lambda` is just a way of defining a function right there instead of defining it elsewhere, and reduce repeatedly applies an operation repeatedly on a list: `reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])` does `((((1+2)+3)+4)+5)`. The reduction by bitwise `OR` over the list of `Q`s is the same as the `any` over the `list` in my answer. – agf Aug 17 '11 at 05:22
  • @agf The difference is here the `OR` is performed at the database level instead of fetching all the records and filtering them in python. – Ismail Badawi Aug 17 '11 at 05:28
  • Really? Django is able to translate the `reduce` into SQL? I think that part is happening in Python inside the ORM. I still wouldn't be suprised if it's faster though. The `in` is happening in the database instead of Python, but the `OR`? – agf Aug 17 '11 at 05:32
  • 1
    @agf Sorry, by the `OR` I meant the actual filtering (i.e. the `WHERE` clause has `name LIKE ... OR name LIKE ...` and so on). The `reduce` only constructs the `Q` object, which doesn't hit the database. – Ismail Badawi Aug 17 '11 at 05:37
  • 6
    Note: If you're using Python > 3, you must import reduce with `from functools import reduce` – primoz Jan 05 '17 at 10:01
33
ob_list = data.objects.filter(name__in=my_list)

And BTW, avoid using the variable name "list" (Or any other python standard keyword), lest you get into some weird bugs later.

Update: (I guess your question was updated too, because when I wrote the answer, I didn't see the part where you wrote you need a contains match and not an exact match)

You can do that using the regex search too, avoiding many Q expressions (which end up using that many where "and" clauses in the SQL, possibly dampening the performance), as follows:

data.objects.filter(name__regex=r'(word1|word2|word3)')
lprsd
  • 84,407
  • 47
  • 135
  • 168
  • 4
    for my purposes the regex approach was much faster than using Q expressions. And made for a much more readable query. Thank you Lakshman Prasad – Ryder Brooks Jun 13 '14 at 03:10
2
obj_list = [obj for obj in data.objects.all() if any(name in obj.name for name in list)]

Edit: Just re-read your question. Don't know if you can do that with filter but you can do it with a list comprehension or generator expression.

agf
  • 171,228
  • 44
  • 289
  • 238
1

For anyone comparing Arrays, you could use Django's Overlap filter to achieve this.

From the docs:

Returns objects where the data shares any results with the values passed. Uses the SQL operator &&.

So, you would simply write:

ob_list = data.objects.filter(name__overlap=my_list)
Danny Vu
  • 101
  • 2
  • 2
0

very simple just use "__in" , as the follwing :

my_list= ['word1','word2','word3']
ob_list = model.objects.filter(your-field__in=my_list)

1- change "model" by your model name.

2- change "your-field" by your field name.

i hope this helpful .

K.A
  • 1,399
  • 12
  • 24
0

Just thought I would add this here. Built this off the "use Q objects" answer with a little tweaking to get search to work for multiple partial word matches across specified fields.

from functools import reduce
from django.contrib.postgres.search import SearchVector
from django.db.models import Q

s = "Input text to match to fields in SearchVector"
words = list(i for i in s.split(" "))
search_data = (
    Item.objects.annotate(
        search=SearchVector(
            "id",
            "name",
        ),
    )
    .filter(
        reduce(
            lambda x, y: x & y,
            [Q(search__contains=word) for word in words],
        )
    )
    .all()
)
Silimadev
  • 1
  • 1