59

I'm trying to query a database based on user input tags. The number of tags can be from 0-5, so I need to create the query dynamically.

So I have a tag list, tag_list, and I want to query the database:

design_list = Design.objects.filter(Q(tags__tag__contains = "tag1") and Q(tags__tag__contains = "tag2") and etc. etc. )

How can I create this feature?

5 Answers 5

132

You'll want to loop through the tag_list and apply a filter for each one.

tag_list = ['tag1', 'tag2', 'tag3']
base_qs = Design.objects.all()
for t in tag_list:
    base_qs = base_qs.filter(tags__tag__contains=t)

This will give you results matching all tags, as your example indicated with and. If in fact you needed or instead, you will probably need Q objects.

Edit: I think I have what you're looking for now.

tags = ['tag1', 'tag2', 'tag3']
q_objects = Q() # Create an empty Q object to start with
for t in tags:
    q_objects |= Q(tags__tag__contains=t) # 'or' the Q objects together

designs = Design.objects.filter(q_objects)

I tested this and it seems to work really well.

Edit 2: Credit to kezabelle in #django on Freenode for the initial idea.

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

7 Comments

Thanks, that makes sense. I've tried it with Q objects, but it doesn't return the correct items. Is there anything I'm doing wrong here: ` design_list = Design.objects.all() for t in tag_list: design_list = design_list.filter(Q(tags__tag__contains = t))`. It works when there's only one tag, but no more.
In your code there, the Q object is not doing anything. You're simple creating a queryset that, in the end, looks like Design.objects.filter(tags__tag__contains='tag1').filter(tags__tag__contains='tag2'), etc. What you probably want instead is Design.objects.filter(Q(tags__tag__contains='tag1') | Q(tags__tag__contains='tag2')), but in a way gives you a variable number of Q objects.
Ok, that's what I need to look for.
If you want designs to be empty when tags is empty, you can use Q(pk__in=[]) as a starting value for q_objects.
It's a shame the QuerySet API doesn't have an "any" method that takes a list of Q objects with "or" semantics, the way filter/exclude/get can take a list of Q objects with "and" semantics.
|
44

You can use this way:

my_dict = {'field_1': 1, 'field_2': 2, 'field_3': 3, ...}  # Your dict with fields
or_condition = Q()
for key, value in my_dict.items():
    or_condition.add(Q(**{key: value}), Q.OR)

query_set = MyModel.objects.filter(or_condition)

By this way you can use dynamically generated field names. Also you can use Q.AND for AND condition.

3 Comments

Is there a way to do this with __in? Like to get a Q that matches a field to a list of values, where the name of the field and the list of values are given by variables instead of literals?
@MichaelHoffmann I do not quite understand what you mean, maybe this: Q(**{"{}__in".format(key): value})
Excellent. Would this single call to filter be faster than calling filter for each condition?
3

Just prepare a tag list first then, query like this:

tags = ['tag1', 'tag2',...]
design_list = Design.objects.filter(tags__tag__contains__in = tags)

Comments

2

You may need to add AND and OR conditions

    query = (Q(fild1='ENABLE'))
    # Filter by list
    query.add(Q(fild2__in=[p.code for p in Objects.field.all()]), Q.AND)

    # filter OR
    q_objects = Q(field3='9999999')
    for x in myList:
        q_objects.add(Q(field3=x.code), Q.OR)

    query.add(q_objects, Q.AND)

Comments

2

Use reduce :

from functools import reduce
design_list = Design.objects.filter(reduce(lambda q1,q2: q1 & q2,
                                           [Q(tags__tag__contains=t)
                                            for t in tag_list]))

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.