40

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!

6 Answers 6

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.

Sign up to request clarification or add additional context in comments.

6 Comments

can you explaing.. reduce and lambda...? Your solution is working fine.
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 Qs is the same as the any over the list in my answer.
@agf The difference is here the OR is performed at the database level instead of fetching all the records and filtering them in python.
@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.
Note: If you're using Python > 3, you must import reduce with from functools import reduce
|
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)')

1 Comment

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
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.

2 Comments

Name should not be in the list, but any word in the list that is contained in the name.
I already edited my answer -- it now does what you want (in the idiomatic Python way, non-Django specific).
2

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)

1 Comment

This only works with PostgreSQL.
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 .

Comments

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()
)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.