Django :: admin search functionality
In-house staff has been getting spoiled lately by the simplicity of the search box in the Django admin interface. My favorite comment from last week was "the stupid thing doesn't even search the middle name field when I enter a name, I wish it could be more like the Django", in reference to an application for which we're paying a significant amount of licencing fees. (On a side-note I would suggest to everyone to re-brand the admin pages -- my users are now convinced Django is the stuff that I've put on their admin page...)
For a number of reasons, however, I'm starting to shift out parts of the admin interface for my own home-made pages. While they've served us well so far, we've gotten to the point now that the feature requests require more custom work than can be integrated into the admin interface. It's a good thing. It means users are thinking about how things can be done better, and it's also the way the Django admin interface is supposed to be used -- allowing everyone to focus on something besides basic admin functionality until later in a project.
It would seem important though, to not take a step backward and lose functionality like e.g. the simplicity of a single search box. I was to lazy to go look at the source, but a quick googling only turned up an entry from Petro Verkhogliad (http://petro.tanreisoftware.com/?p=22) that only deals with searching a single field (and a suggestion for an algorithm to search multiple fields that materializes way to much data to be practical). The second entry I found was from Steven Ametjan (http://www2.wolfsreign.com/archives/2007/01/22/writing-search-view-django/). His solution is unfortunately buggy if there is more than one search term.
A correct version should look something like this:
-
from django.db.models import Q
-
-
def search(terms=None):
-
if terms is None:
-
return Customers.objects.all()
-
-
query = Customer.objects
-
for term in terms:
-
query = query.filter(
-
Q(fname__icontains=term)
-
| Q(lname__icontains=term)
-
| Q(email__icontains=term)
-
| Q(zipcode__icontains=term)
-
| Q(birthdate=term))
-
return query
That works ok, but searching the birthdate field requires using database syntax to enter dates (2007-03-25). People get very upset when they can't enter dates in their local format (I can't emphasize this too much). Where I'm sitting right now, dates are always entered as dd.mm.yyyy. We can fix this problem though, and at the same time make our searches faster by utilizing some domain knowledge. In this case we know that we don't need to search in non-date fields for terms that are dates (nor for non-date data in date fields). The only numeric column we're searching is the zipcode column, so we can limit terms that are matched against this column as well, and we end up with something like this:
-
def possible_zipcode(v):
-
try:
-
int(v)
-
return True
-
except:
-
return False
-
-
def local_date_format(v):
-
if len(v) == 10 and len(v.split('.')) == 3:
-
try:
-
day, month, year = map(int, v.split('.'))
-
datetime.date(year, month, day)
-
return True
-
except:
-
pass
-
return False
-
-
-
def search(terms=None):
-
if terms is None:
-
return Customers.objects.all()
-
-
query = Customer.objects
-
for term in terms:
-
if possible_zipcode(term):
-
query = query.filter(zipcode=term)
-
elif local_date_format(term):
-
day, month, year = map(int, term.split('.'))
-
query = query.filter(birthdate=datetime.date(year, month, day))
-
elif '@' in term:
-
query = query.filter(email__icontains=term)
-
else:
-
query = query.filter(
-
Q(fname__icontains=term)
-
| Q(lname__icontains=term))
-
return query
For my real code running against real data this approach turned out to be an order of magnitude faster than Django's generic algorithm... (In all fairness to Django I should probably mention that the only time Django's search box isn't almost instantaneous is when I'm running the admin interface on my personal machine against our production database over a vpn connection from home