Ken Bellows
Ken Bellows

Reputation: 6930

Django: How do I use a string as the keyword in a Q() statement?

I'm writing a simple search form for a certain model. Let's call the model Orchard and give it the attributes apples, oranges, and pears, just for the sake of demonstration.

So, the form does not require all fields to be filled. So you can search on apples and oranges but not pears. I them need to filter like this:

Orchard.objects.filter(apples=request.GET.get('apples'), oranges=request.GET.get('oranges'), pears=request.GET.get('pears'))

but if pears is empty, no results will ever return.

My first thought was to use Q objects, something like this:

from django.db.models import Q

options = {}
options['apples'] = request.GET.get('apples')
options['oranges'] = request.GET.get('oranges')
options['pears'] = request.GET.get('pears')

queries = None

for key in options:
    if options[key] != u'':
        if queries:
            queries &= Q(key=options[key]) # <=== problem here
        else:
            queries = Q(key=options[key])  # <=== same problem here

results = Orchard.objects.filter(queries)

The problem comes up in those marked lines. I obviously can't just use "key" as the attribute keyword, because it doesn't take a string, it takes essentially a variable.

So... how do I get around this?

Unless there's a known solution to this problem not involving Q. That would be helpful too.

Upvotes: 15

Views: 14020

Answers (2)

Alasdair
Alasdair

Reputation: 308779

@second's answer is correct, unpack a dictionary with the ** operator to provide keyword arguments.

However, if you are only using AND to combine Q objects and not OR, then you don't actually need to use Q objects in your example. Just build a dictionary of the lookups, then use that as the keyword arguments for filter.

options = {}
for key in ('apples', 'oranges', 'pears'):
    value = request.GET.get(key)
    if value:
        options[key] = value
results = Orchard.objects.filter(**options)

Upvotes: 5

second
second

Reputation: 28637

this is a general issue with using a variable as the key in a keyword arg. the solution is to wrap things in a dict and unpack it:

queries &= Q(**{key: options[key]})

or in your case

for option in options:
    if options[option] is None:
        del(options[option])
# or otherwise only add the ones you actually want to filter on
# then
results = Orchard.objects.filter(**options)

Upvotes: 29

Related Questions