Reputation: 6930
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
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
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