class SearchLet(Form):
district = Field(widget=SelectMultiple(choices=DISTRICTS, required=False)
min_price = IntegerField(widget=TextInput(required=False)
max_price = IntegerField(widget=TextInput(required=False)
#def clean_district...
def search_let(request):
if request.method == "POST":
form = SearchLet(request.POST)
if form.is_valid():
#bind data to dict keys
qparams = dict(
district = form.cleaned_data["district"],
min_price = form.cleaned_data["min_price"],
max_price = form.cleaned_data["max_price"],
)
#keep list Q
qsets = dict(
district = Q(property__district__in = qparams["district"]),
min_price = Q(property__houselet__rent_price__gte = qparams["min_price"]),
max_price = Q(property__houselet__rent_price__lte = qparams["max_price"])
)
#filter out the Q that have None values
final_query = [qsets[k] for k in qsets if qparams[k] is not None]
custom_search_results = Ad.objects.select_related().filter(*final_query)
context = dict(form=form, custom_search_results=custom_search_results)
else:
pass #handle invalid form
else:
pass #handle GET
return render_to_response("search_house_let.html", context)
Refactorings
No refactoring yet !
akaihola
October 9, 2008, October 09, 2008 12:12, permalink
Closing paretheses missing from fields in class SearchLet.
Usually people "from django import forms" instead of objects inside the module.
"required=" is a keyword argument of the field, not the widget.
The default widget for IntegerField is TextInput. No need to specify that.
Here's my stab at adding reusability:
#!/usr/bin/python
from django import forms
from django.db.models.query import QuerySet
class EasyFilter(object):
def __init__(self, **kwargs):
try:
self.query_kwargs = dict((self.filters[key], value)
for key, value in kwargs.iteritems())
except KeyError:
raise KeyError('Invalid field(s) in %r' % kwargs)
def get_query_set(self):
return self.model._default_manager.filter(**self.query_kwargs)
class AdFilter(EasyFilter):
model = Ad
filters = dict(district='property__district__in',
min_price='property__houselet__rent_price__gte',
max_price='property__houselet__rent_price__lte')
class SearchLet(forms.Form):
district = forms.Field(widget=forms.SelectMultiple(choices=DISTRICTS), required=False)
min_price = forms.IntegerField(required=False)
max_price = forms.IntegerField(required=False)
#def clean_district...
def search_let(request):
if request.method == "POST":
form = SearchLet(request.POST)
if form.is_valid():
custom_search_results = Adfilter(**form.cleaned_data).get_query_set().select_related()
context = dict(form=form, custom_search_results=custom_search_results)
else:
pass #handle invalid form
else:
pass #handle GET
return render_to_response("search_house_let.html", context)
Ajit
May 25, 2010, May 25, 2010 11:05, permalink
Good work akaihola. I used this code to implement search for a variety of models in my project. But we found that when the search argument field is passed blank from the front end, your code doesnt remove that argument from the Final Search Query. The previous version by lbolognini had the statement which removed the blank parameters from the Query with none values (line 30).
So we made a small change in one line of EasyFilter class due to which the blank (blank charfields and multipleselect fields parameters are handled properly)
class EasyFilter(object):
def __init__(self, **kwargs):
try:
self.query_kwargs = dict((self.filters[key], value)
for key, value in kwargs.iteritems() if kwargs [key] != [] and kwargs[key] != '')
except KeyError:
raise KeyError('Invalid field(s) in %r' % kwargs)
def get_query_set(self):
return self.model._default_manager.filter(**self.query_kwargs)
Ajit
May 25, 2010, May 25, 2010 11:11, permalink
Good work akaihola. I used this code to implement search for a variety of models in my project. But we found that when the search argument field is passed blank from the front end, your code doesnt remove that argument from the Final Search Query. The previous version by lbolognini had the statement which removed the blank parameters from the Query with none values (line 30).
So we made a small change in one line of EasyFilter class due to which the blank (blank charfields and multipleselect fields parameters are handled properly)
class EasyFilter(object):
def __init__(self, **kwargs):
try:
self.query_kwargs = dict((self.filters[key], value)
for key, value in kwargs.iteritems() if kwargs [key] != [] and kwargs[key] != '')
except KeyError:
raise KeyError('Invalid field(s) in %r' % kwargs)
def get_query_set(self):
return self.model._default_manager.filter(**self.query_kwargs)
I'm using the following code (shortened for readability) to search inside Django models.
On occasions, i had people telling me they would use the same principle but would make it better, but they didn't bother entering into details.
The code below does not "scale" too well, by that meaning that it's hard to adapt to different models, it's difficult to test and get's messy when you start adding many fields.
Can you make it better? This is the piece of Django i'm less satisfied with, but having never heard anybody complaining too loudly about it i think i might be missing something.
One thing i have in mind might be making a custom manager? Would that fit this use case?