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.
F() expressions. An F() object represents the value of a model field, transformed value of a model field, or annotated column. It makes it possible to refer to model field values and perform database operations using them without actually having to pull them out of the database into Python memory.
¶ Django allows using SQL subqueries.
`OuterRef` is an "outer reference", a construct created to allow a reference to the query surrounding the current subquery.
Join QueriesJoin can be done with select_related method: Django defines this function as Returns a QuerySet that will “follow” foreign-key relationships, selecting additional related-object data when it executes its query.
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)
@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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With