Reputation: 428
For a real-estate listing site I have a search form with multiple inputs. The user can use one or many fields to filter his search results. The more fields are filled in the more detailed the search should get.
For example: If you only input the zip code all listings that match the zip code should be shown, if you input the zip code and the room number only listings that match both should be shown. My forms.py looks like this:
from django import forms
from .models import Property
class PropertyForm(forms.Form):
zip_code = forms.CharField(required=False)
rooms = forms.CharField(required=False)
Because I need a AND search I had to write a series of conditions in my ListView to query the results:
My views.py looks like this:
class ListPageView(ListView):
template_name = 'property_list.html'
model = Property
def get_queryset(self):
plz = self.request.GET.get('plz')
rooms = self.request.GET.get('rooms')
if plz and not rooms:
object_list = Property.objects.filter(zip_code__exact=plz)
elif rooms and not plz:
object_list = Property.objects.filter(rooms__exact=rooms)
elif plz and rooms:
object_list = Property.objects.filter(
Q(zip_code__icontains=plz) & Q(rooms__exact=rooms)
)
else:
object_list = self.model.objects.none()
return object_list
For only two input fields this would suffice but if I add more input fields I quickly have to write a lot of queries. Is there a better way to make AND queries that automatically exclude empty values?
Upvotes: 0
Views: 86
Reputation: 320
Use optional fields in And operation in django queryset by Q and reduce:
import operator
from django.db.models import Q
import functools
class ListPageView(ListView):
template_name = 'property_list.html'
model = Property
def get_queryset(self):
fields = ["plz", "rooms"]
predicates = []
for field in fields:
key=field+"__exact"
value=self.request.GET.get(field)
if value:
predicates.append((key,value))
queryList = [Q(x) for x in predicates]
queryset = Property.objects.filter(functools.reduce(operator.and_, queryList))
return queryset
Upvotes: 2
Reputation: 29967
Instead of creating a queryset for each possible combination of parameters, you can start with an unfiltered queryset and add filters step-by-step:
class ListPageView(ListView):
template_name = 'property_list.html'
model = Property
def get_queryset(self):
plz = self.request.GET.get('plz')
rooms = self.request.GET.get('rooms')
if any([plz, rooms]):
object_list = Property.objects.all()
if plz:
object_list = object_list.filter(zip_code__exact=plz)
if rooms:
object_list = object_list.filter(rooms__exact=rooms)
else:
object_list = Property.objects.none()
return object_list
To make this less repetitive, you could use a loop:
class ListPageView(ListView):
template_name = 'property_list.html'
model = Property
def get_queryset(self):
field_mapping = ( # mapping field names to parameter names
('zip_code', 'plz'),
('rooms', 'rooms'),
)
# Building a dictionary with the parameters, equal to:
# params = {
# 'zip_code': self.request.GET.get('plz'),
# 'rooms': self.request.GET.get('rooms'),
# }
params = {
field_name: self.request.GET.get(param_name)
for field_name, param_name
in field_mapping
}
if any(params.values()):
object_list = Property.objects.all()
for field_name, value in params.items():
if value:
# Passing a dictionary with one dynamically
# built key as keyword arguments to filter
object_list = object_list.filter(
**{f"{field_name}__exact":value}
)
else:
object_list = Property.objects.none()
return object_list
Upvotes: 2