I want users to be able to query the database via different rows (name, year, etc.), with the option to have multiple fields connected with boolean operators, e.g. "year 1900 AND name chicken OR location San Francisco." I'm using Q objects and reduce(), but I've clearly made some mistake, since when I try just one field with a value I know should return stuff (e.g. through making the same query via the django shell), I get nothing. Anyone have any idea what I'm doing wrong/how I could fix it?
Here's my view:
def search(request):
errors = []
searchFormSet = formset_factory(F.SearchForm, extra=1,formset=BaseSearchFormSet)
if request.GET:
formset = searchFormSet(request.GET)
forms = []
if formset.is_valid():
for x in xrange(0,formset.total_form_count()):
form = {}
form["row"]= formset[x].cleaned_data.get("row",None)
form["query"] = formset[x].cleaned_data.get("query",None)
form["bools"] = formset[x].cleaned_data.get("bools",None)
if form["query"]:
q = form["query"]
else:
errors.append("no query found")
if form["row"]:
row = form["row"]
else:
errors.append("no row found")
filter_keys = {"dish_name":Q(dish__name__icontains=q),
"regex":Q(dish__full_name__regex=r'%s'%(q)),
"course":Q(dish__classification=q.lower()),
"year":Q(page__menu_id__year__exact=q),
"period":Q(page__menu_id__period__exact=q),
"location":Q(page__menu_id__location__icontains=q),
"restaurant":Q(page__menu_id__restaurant__icontains=q)}
forms.append(form)
final_query=Q()
def var_reduce(op,slice):
if op == "and":
return reduce(lambda x,y: x & y,slice)
elif op == "or":
return reduce(lambda x,y: x | y,slice)
for x in xrange(len(forms)):
try:
try:
if final_query:
slice = [final_query,filter_keys[forms[x]["row"]],filter_keys[forms[x+1]["row"]]]
else:
slice = [filter_keys[forms[x]["row"]],filter_keys[forms[x+1]["row"]]]
final_query = var_reduce(forms[x]["bools"],slice)
except IndexError:
if final_query:
slice = [final_query,filter_keys[forms[x]["row"]]]
else:
slice = [filter_keys[forms[x]["row"]]]
final_query = var_reduce(forms[x]["bools"],slice)
items = MenuItem.objects.filter(final_query)
# classifications = {}
# for dish in dishes:
# classifications["{d}".format(dish)] = dish.dish_set.all().classification_set.all()
return render_to_response("search_results.html",{"items":items,"formset":formset})
except KeyError as e:
errors.append(e)
formset = searchFormSet(None)
return render_to_response("search_page.html",{"errors":errors,"formset":formset})
else:
formset = searchFormSet(None)
return render_to_response("search_page.html",{"errors":errors,"formset":formset})
else:
formset = searchFormSet(None)
return render_to_response("search_page.html",{"formset":formset})
Here're my models:
from django.db import models
# Create your models here.
# class MenuItemManager(models.Manager):
# def create_MenuItem(self,dish,price,course,page,mk):
# menuItem = self.create(dish=str(dish),price=str(price),page=str(page),mk=str(mk))
# return menuItem
class MenuItem(models.Model):
def format_price(self):
return "${0:0<4,.2f}".format(float(self.price))
def __unicode__(self):
return self.dish
dish=models.OneToOneField('Dish',to_field='mk')
price=models.CharField(max_length=5,blank=True)
# course=models.CharField(max_length=100,unique=False)
page=models.OneToOneField('MenuPage')
mk=models.CharField(max_length=10,unique=True)
formatted_price = property(format_price)
#objects=MenuItemManager()
# def __unicode__(self):
# return name
# class MenuManager(models.Manager):
# def create_Menu(self,restaurant,year,location,status,mk,period,language):
# menu = self.create(restaurant=str(restaurant),year=int(year),location=str(location),status=str(status),mk=int(mk),period=str(period),language=str(language))
# return menu
class Menu(models.Model):
try:
p=int(10*round(float(int(self.year))/10))
if p < self.year:
return "%s-%s"%(p,p+5)
else:
return "%s-%s"%(p-5,p)
except (ValueError,TypeError):
return ""
def __unicode__(self):
if self.restaurant:
return self.restaurant
else:
restaurant=models.TextField(unique=False,blank=True,null=True)
year=models.CharField(max_length=4,unique=False,blank=True,null=True)
location=models.TextField(unique=False,blank=True,null=True)
status=models.CharField(unique=False,max_length=20)
mk=models.CharField(max_length=8,unique=True,primary_key=True)
period=property(period)
language = models.CharField(unique=False,max_length=30)
#objects=MenuManager()
# def __unicode__(self):
# return restaurant
# class MenuPageManager(models.Manager):
# def create_MenuPage(self,mk,menu_id):
# menuPage = self.create(mk=int(mk),menu_id=int(menu_id))
# return menuPage
class MenuPage(models.Model):
mk=models.CharField(max_length=10,unique=True)
menu_id=models.OneToOneField("Menu",to_field='mk')
#objects=MenuPageManager()
# class DishManager(models.Manager):
# def create_Dish(self,name,mk):
# dish = self.create(name=str(name),mk=int(mk))
# return dish
# def get_by_natural_key(self,name):
# return self.get(name=name)
class Dish(models.Model):
def __unicode__(self):
full_name = models.TextField()
name=models.CharField(unique=True,max_length=255)
mk=models.CharField(max_length=10,unique=True)
#classification = models.OneToOneField("Classification",to_field="d")
#objects=DishManager()
# def __unicode__(self):
# return name
# class ClassificationManager(models.Manager):
# def create_Classification(self,dish,classification,mk):
# classification = self.create(dish=str(dish),classification=str(classification),mk=int(mk))
# return classification
class Classification(models.Model):
def __unicode__(self):
if self.classification:
return self.classification
else:
return "none"
dish=models.OneToOneField('dish',to_field='name')
classification=models.CharField(unique=False,max_length=9)
mk=models.CharField(max_length=10,primary_key=True)
Here, if it's relevant, is the template for my search page:
{% extends "base.html" %}
{% block style %}
<link rel="stylesheet" type="text/css" href="/static/search_style.css" />
{% endblock %}
{% block java %}
<script type="text/javascript" src="/static/searches.js"></script>
{% endblock %}
{% block title %}Search{% endblock %}
{% block head %}Search{% endblock %}
{% block content %}
{% autoescape off %}
<div id="searches">
<form id="search" action="" method="get">
<table border="0" cellpadding="0" cellspace="0">
<tbody class="search">
{% for form in formset.forms %}
<tr>
<td class="row">{{ form.row }}</td>
<td class="query">{{ form.query }}</td>
<td class="bool">{{ form.bools }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ formset.management_form }}
<input type="submit" value="Submit" id="submit">
</form>
</div>
{% if formset.errors or errors %}
<div id="errors">
<h3>The following errors were encountered while trying to submit your search:</h3>
{% for x,y in formset.errors.items %}
<p>{{ x }} : {{ y }}</p>
{% endfor %}
{{ errors }}
</div>
{% endif %}
<div id="notes">
<p>Searching by dish names, locations, and restaurants is case-insensitive.</p>
<p>Searching by course uses case-insensitive exact matching. Valid courses are Appetizer, Main, and Dessert.</p>
<p>Years should be entered YYYY. Five-year periods span either the first or second half of a decade, and should be entered YYYY-YYYY. Example valid five-year periods are 1900-1905, 1995-2000, etc.</p>
</div>
{% endautoescape %}
<br /><br /><br /><br /> <br /><br /><br />
{% endblock %}
{% block footer %}
<div id="warning">
<p>NOTE: This site and the information it contains are still in development. Some information may be missing or inaccurate.</p>
</div>
<div class="credits">
Created and maintained by <a href="/about#sam">Sam Raker</a> and <a href="/about#rachel">Rachel Rakov</a>
<br />
</div>
{% endblock %}