Django parallel arrays in a template - python

I have 2 arrays that I would like to render in a template, one is the data to be output, the other is a formset for deleting items. since it seems django does not support boolean operators in the template tag, I have tried packaging the items, but they return the first item and the first form in 2 rows only.
How does one package such items so that they are rendered in one for loop.
my view
#login_required
def forums(request ):
post = list(forum.objects.filter(child='0')&forum.objects.filter(deleted='0'))
user = UserProfile.objects.get(pk=request.session['_auth_user_id'])
newpostform = PostForm(request.POST)
deletepostform = PostDeleteForm(request.POST)
DelPostFormSet = modelformset_factory(forum, exclude=('child','postSubject','postBody','postPoster','postDate','childParentId'))
readform = ReadForumForm(request.POST)
if newpostform.is_valid():
topic = request.POST['postSubject']
poster = request.POST['postPoster']
newpostform.save()
newpostform = PostForm(initial = {'postPoster':user.id})
post = list(forum.objects.filter(child='0')&forum.objects.filter(deleted='0'))
else:
newpostform = PostForm(initial = {'postPoster':user.id})
if request.method == 'POST':
delpostformset = DelPostFormSet(request.POST)
if delpostformset.is_valid():
delpostformset.save()
else:
delpostformset = DelPostFormSet(queryset=forum.objects.filter(child='0', deleted='0'))
"""if readform.is_valid():
readform.save()
else:
readform = ReadForumForm()"""
return render_to_response('forum.html', {'post':( post,delpostformset.forms), 'newpostform': newpostform, })
my Template
<table class="results">
<tr class="inner_results_header"><td >Title</td><td>Date/Time</td><td>Poster</td> <td>Body</td><td><form method="post" id="form" action="" class="usertabs accfrm"><input type="submit" value="Delete" /></td></tr>
{{formset.management_form}}
{% for p, form in post %}
{% url forum_view p.postID as post_url%}
<tr class="inner_results {% if forloop.counter|divisibleby:2 %}evens{% else %}odds{% endif %}"><span onclick="document.location.href='{{post_url}}';"><td>{{ p.postSubject}}</td><td>{{p.postDate}}</td><td>{{ p.postPoster}}</td><td>{{ p.postBody|truncatewords:50}}</td></span><td>
{{ form.as_p }}
</td></tr>
{% endfor %}
<tr class="inner_results_header"><td >Title</td><td>Date/Time</td><td>Poster</td> <td>Body</td><td><input type="submit" value="Delete" /></form></td></tr>

Use zip builtin. If both post and delpostformset.forms are iterables, zip will return a list of tuples. In view:
post_and_form = zip(post, delpostformset.forms)
and in template:
{% for post, form in post_and_form %}
{% endfor %}

Related

How to validate an inline formset within a create view

I have an inline formset and 2 different forms within a create view of one of the forms.
All my fields in the model are blank=False and the inline formset has a minimum validation of 1.
If I try to send the form without any fields from the create view form, it displays the classic message "Please fill out this field" without rerendering the page. I want to achieve the same thing when the formset is not filled correctly. Also the same for the second form (this form is only displayed when the formset is filled based on the choice).
I am able to get the error messages re rendering the form but I'd like to do it before sending it to the server. Is it possible changing something in the model or I need to do it via js on the front?
Many thanks.
Views:
class SolicitacionReferenciaCreate(CreateView):
template_name = 'microcredito/solicitacion_form.html'
model = ContactoInfo
fields = ['nombre', 'destinoPrestamo', 'apellido']
success_url = reverse_lazy('accounts/login')
def get_context_data(self, **kwargs):
data = super(SolicitacionReferenciaCreate, self).get_context_data(**kwargs)
referencias_form_1 = ReferenciaForm()
data['referencias_form_1'] = referencias_form_1
if self.request.POST:
data['plataformas'] = IngresosFormSet(self.request.POST)
else:
data['plataformas'] = IngresosFormSet()
return data
def form_valid(self, form):
context = self.get_context_data()
plataformas = context['plataformas']
with transaction.atomic():
#ContactoInfo form
if plataformas.is_valid() and form.is_valid():
print("VALID")
self.object = form.save()
plataformas.instance = self.object
cuposPlataformas = []
for f in plataformas:
cd = f.cleaned_data
try:
score = ScorePersonas.objects.filter(idPlataforma=cd.get('idPlataforma'), numberPlataforma=cd.get('number_plataforma'),
aprobado=1)[0]
cuposPlataformas.append(score.cupo)
except:
try:
score = ScorePersonas.objects.filter(idPlataforma=cd.get('idPlataforma'), numberPlataforma=cd.get('number_plataforma'),
aprobado=0)[0]
cuposPlataformas.append(0)
except:
pass
plataformas.save()
self.object.save()
referenciaCreado1 = Referencia.objects.create_Referencia(self.object, plataformaObjeto, rt_referenciado_1, celular_referenciado_1,
nombre_referencia, apellido_referencia)
referenciaCreado1.save()
else:
#messages.error(request, "Error")
return self.render_to_response(self.get_context_data(form=form))
return render(self.request, 'microcredito/solicitacion_form.html', context)
Template:
<form action="" method="post" id="contactForm">
{% csrf_token %}
{{ form.non_field_errors }}
{{ form.source.errors }}
{{ form.source }}
{{ form.nombre.errors }}
{{ form.nombre|as_crispy_field }}
<table class="table">
{{ plataformas.management_form }}
{% for form in plataformas.forms %}
{% if forloop.first %}
<thead>
<tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr>
</thead>
{% endif %}
<tr class="{% cycle row1 row2 %} formset_row">
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<div class="form-row hide-element-form" id="ref_div_id_1">
<div class="form-group col-md-6 mb-0">
{{ referencias_form_1.numberPlataforma_referencia|as_crispy_field }}
</div>
</div>
<input onsubmit="formSent" id="form_button" type="submit" value="Save"/>
</form>
Formset:
IngresosFormSet = inlineformset_factory(ContactoInfo, IngresosPersonas,
form=IngresosForm, extra=1, min_num=1, max_num=8, validate_min=True)

Django Form Wizard Render to Response on Step

I am working with the Django form wizard and i am trying to emulate the typical
render_to_response('index.html',{'name', name})
but without redirect and only in the steps.
So the idea would be i have a form wizard like this
TEMPLATES = {"0": "MyApp/checkout_template1.html",
"1": "MyApp/checkout_template2.html",
"2": "MyApp/checkout_template3.html",
}
class MyWizard(SessionWizardView):
def get_template_names(self):
return [TEMPLATES[self.steps.current]]
def get_context_data(self, form, **kwargs):
context = super(MyWizard, self).get_context_data(form=form, **kwargs)
if self.steps.current == '3':
data_1 = self.get_cleaned_data_for_step('1')
((HERE I NEED TO PASS THE data_1["first_name"] TO SHOW ON NEXT TEMPLATE))
print(data_1["first_name"])
return context
def done(self, form_list, **kwargs):
if self.request.method == 'POST':
process_form_data(form_list)
return render(self.request,'MyApp/congratulations.html')
then in templates id have something like this
{% extends "MyApp/base.html" %}
{% load staticfiles %}
{% load humanize %}
{% block head %}{{ wizard.form.media }}{% endblock %}
{% endblock %}
{% block body_block%}
<div class="confirmation-body">
<div class="confirmation-body-container">
<div class="form-title-container">
<h1>Information</h1>
<p>Step {{ wizard.steps.step1 }} of {{ wizard.steps.count }}</p>
<form action="/checkout/" method="post">{% csrf_token %}
<table>
{{ wizard.management_form }}
{% if wizard.form.forms %}
{{ wizard.form.management_form }}
{% for form in wizard.form.forms %}
{{form}}
{% endfor %}
{% else %}
<div class="checkout-title">Information</div>
//////////////////////
{{ name }} #something like this that prints in the next step
//////////////////////
{{wizard.form.name_on_card}}
{% endif %}
</table>
<hr />
<div class="checkout-nav-controls">
<div>
{% if wizard.steps.prev %}
<button id="prev-step-button" name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}">back</button>
{% endif %}
</div>
<input id="my-submit-button" type="submit" value="Next"/>
</div>
</form>
</div>
</div>
{% endblock %}
So just to reiterate the question for clairity. I want to:
get the data from the previous step (lets say first step and name)
in the second step pass that data to the corresponding html
template {{name}}
render that last step so i can see the name on
the front end
thanks in advance for your help
Maybe something like this whould do the trick, using get_form method:
def get_form(self, step=None, data=None, files=None):
form = super(MyWizard, self).get_form(step, data, files)
if step is None:
step = self.steps.current
if step == '3':
data_1 = self.get_cleaned_data_for_step('1')['first_name']
form.fields['first_form_field'].label = data1
return form
So in this way, you will have the field first_name from the first steps' form, as a label to the third step's form's first field
You can supply additional context variables by using the get_context_data() method of your WizardView subclass.
def get_context_data(self, form, **kwargs):
context = super(MyWizard, self).get_context_data(form=form, **kwargs)
if self.steps.current == 'my_step_name':
category=self.get_cleaned_data_for_step('category')['name']
context.update({'another_var': 'category'})
return context
get_context_data() docs

Django - How to check if id contains in a table column?

guys.
I want to check in my django template, if request.user exists in some row of column user in my table LeagueMember. The way I found is not working.
views.py
#login_required(login_url='login/')
def search_leagues(request):
if request.method == 'POST':
return redirect('join_league')
leagues = League.objects.all()
return render(request, 'search_leagues.html', { 'allleagues': leagues })
model.py
class League(models.Model):
league_owner = models.ForeignKey('auth.User')
league_name = models.CharField(max_length=30)
creation_date = models.DateTimeField()
def is_member(self):
member = LeagueMember.objects.get(league=self)
if member:
return True
else:
return False
class LeagueMember(models.Model):
league = models.ForeignKey('League', related_name='leaguemember_league')
user = models.ForeignKey('auth.User')
search_leagues.html
{% for league in allleagues %}
<tr>
<td class="center">{{ league.league_name }}</td>
<td class="center">{{ league.leaguemember_league.count}}/{{ league.leaguesettings_league.league_number_teams }}</td>
<td class="center">{{ league.leaguesettings_league.league_eligibility }}</td>
<td class="center">{{ league.leaguesettings_league.league_lifetime }}</td>
{% if request.user in league.leaguemember_league.user %}
DO SOMETHING!!!
{% else %}
{% if league.leaguemember_league.count < league.leaguesettings_league.league_number_teams %}
{% if league.leaguesettings_league.league_eligibility == "Private" %}
<form method="post" action="{% url 'joinleague' pk=league.id %}">
<td class="center">Soliticar</td>
</form>
{% elif league.leaguesettings_league.league_eligibility == "Public" %}
<form method="post" action="{% url 'joinleague' pk=league.id %}">
<td class="center">Entrar</td>
</form>
{% endif %}
{% endif %}
{% endif %}
</tr>
{% endfor %}
This error is in this line:
{% if request.user in league.leaguemember_league.user %}
Always goes to ELSE block
Thank you all
league.leaguemember_league will not give you a LeagueMember object but a RelatedManager object (so you cannot find a user property in it, therefore your template logic will not work).
What you are trying to do is go two levels deep in your relationship (League -> LeagueMember -> User). You can't easily do this kind of logic in your template and probably need to do it in your view code instead. For example:
league_data = []
for league in League.objects.all():
league_data.append({
'league': league,
'users': User.objects.filter(leaguemember__league=league) # This gives you all the users that are related to this league
})
return render(request, 'search_leagues.html', { 'allleagues': league_data})
You then need to modify all your template logic to use this new structure:
{% for league_data in allleagues %}
# Replace league with league_data.league in all the template logic below this
In the if block you can then do:
{% if request.user in league_data.users %}
Note that this querying may not be very efficient if you have a large number of users/leagues - in which case you may need to rethink your model design.

Use django-import-export with class based views

I want to use django-import-export with class based views.
In the docs at https://django-import-export.readthedocs.org/en/latest/getting_started.html I see an example of exporting as csv
>>> dataset = BookResource().export()
>>> print dataset.csv
id,name,author,author_email,imported,published,price,categories
2,Some book,1,,0,2012-12-05,8.85,1
but which class based view should I use if I want to return an Excel file? Just View?
Jamgreen,
Based on implementation at https://github.com/bmihelac/django-import-export and considering a model "Country" as example with name and abreviation atributes:
First, define at the end of Country models file the Resource:
class CountryResource(resources.ModelResource):
class Meta:
model = Country
Then, implement the class based views:
class CountryExport(View):
def get(self, *args, **kwargs ):
dataset = CountryResource().export()
response = HttpResponse(dataset.csv, content_type="csv")
response['Content-Disposition'] = 'attachment; filename=filename.csv'
return response
class CountryImport(View):
model = Country
from_encoding = "utf-8"
#: import / export formats
DEFAULT_FORMATS = (
base_formats.CSV,
base_formats.XLS,
base_formats.TSV,
base_formats.ODS,
base_formats.JSON,
base_formats.YAML,
base_formats.HTML,
)
formats = DEFAULT_FORMATS
#: template for import view
import_template_name = 'Country/import.html'
resource_class = None
def get_import_formats(self):
"""
Returns available import formats.
"""
return [f for f in self.formats if f().can_import()]
def get_resource_class(self):
if not self.resource_class:
return modelresource_factory(self.model)
else:
return self.resource_class
def get_import_resource_class(self):
"""
Returns ResourceClass to use for import.
"""
return self.get_resource_class()
def get(self, *args, **kwargs ):
'''
Perform a dry_run of the import to make sure the import will not
result in errors. If there where no error, save the user
uploaded file to a local temp file that will be used by
'process_import' for the actual import.
'''
resource = self.get_import_resource_class()()
context = {}
import_formats = self.get_import_formats()
form = ImportForm(import_formats,
self.request.POST or None,
self.request.FILES or None)
if self.request.POST and form.is_valid():
input_format = import_formats[
int(form.cleaned_data['input_format'])
]()
import_file = form.cleaned_data['import_file']
# first always write the uploaded file to disk as it may be a
# memory file or else based on settings upload handlers
with tempfile.NamedTemporaryFile(delete=False) as uploaded_file:
for chunk in import_file.chunks():
uploaded_file.write(chunk)
# then read the file, using the proper format-specific mode
with open(uploaded_file.name,
input_format.get_read_mode()) as uploaded_import_file:
# warning, big files may exceed memory
data = uploaded_import_file.read()
if not input_format.is_binary() and self.from_encoding:
data = force_text(data, self.from_encoding)
dataset = input_format.create_dataset(data)
result = resource.import_data(dataset, dry_run=True,
raise_errors=False)
context['result'] = result
if not result.has_errors():
context['confirm_form'] = ConfirmImportForm(initial={
'import_file_name': os.path.basename(uploaded_file.name),
'input_format': form.cleaned_data['input_format'],
})
context['form'] = form
context['opts'] = self.model._meta
context['fields'] = [f.column_name for f in resource.get_fields()]
return TemplateResponse(self.request, [self.import_template_name], context)
def post(self, *args, **kwargs ):
'''
Perform a dry_run of the import to make sure the import will not
result in errors. If there where no error, save the user
uploaded file to a local temp file that will be used by
'process_import' for the actual import.
'''
resource = self.get_import_resource_class()()
context = {}
import_formats = self.get_import_formats()
form = ImportForm(import_formats,
self.request.POST or None,
self.request.FILES or None)
if self.request.POST and form.is_valid():
input_format = import_formats[
int(form.cleaned_data['input_format'])
]()
import_file = form.cleaned_data['import_file']
# first always write the uploaded file to disk as it may be a
# memory file or else based on settings upload handlers
with tempfile.NamedTemporaryFile(delete=False) as uploaded_file:
for chunk in import_file.chunks():
uploaded_file.write(chunk)
# then read the file, using the proper format-specific mode
with open(uploaded_file.name,
input_format.get_read_mode()) as uploaded_import_file:
# warning, big files may exceed memory
data = uploaded_import_file.read()
if not input_format.is_binary() and self.from_encoding:
data = force_text(data, self.from_encoding)
dataset = input_format.create_dataset(data)
result = resource.import_data(dataset, dry_run=True,
raise_errors=False)
context['result'] = result
if not result.has_errors():
context['confirm_form'] = ConfirmImportForm(initial={
'import_file_name': os.path.basename(uploaded_file.name),
'input_format': form.cleaned_data['input_format'],
})
context['form'] = form
context['opts'] = self.model._meta
context['fields'] = [f.column_name for f in resource.get_fields()]
return TemplateResponse(self.request, [self.import_template_name], context)
class CountryProcessImport(View):
model = Country
from_encoding = "utf-8"
#: import / export formats
DEFAULT_FORMATS = (
base_formats.CSV,
base_formats.XLS,
base_formats.TSV,
base_formats.ODS,
base_formats.JSON,
base_formats.YAML,
base_formats.HTML,
)
formats = DEFAULT_FORMATS
#: template for import view
import_template_name = 'Country/import.html'
resource_class = None
def get_import_formats(self):
"""
Returns available import formats.
"""
return [f for f in self.formats if f().can_import()]
def get_resource_class(self):
if not self.resource_class:
return modelresource_factory(self.model)
else:
return self.resource_class
def get_import_resource_class(self):
"""
Returns ResourceClass to use for import.
"""
return self.get_resource_class()
def post(self, *args, **kwargs ):
'''
Perform the actual import action (after the user has confirmed he
wishes to import)
'''
opts = self.model._meta
resource = self.get_import_resource_class()()
confirm_form = ConfirmImportForm(self.request.POST)
if confirm_form.is_valid():
import_formats = self.get_import_formats()
input_format = import_formats[
int(confirm_form.cleaned_data['input_format'])
]()
import_file_name = os.path.join(
tempfile.gettempdir(),
confirm_form.cleaned_data['import_file_name']
)
import_file = open(import_file_name, input_format.get_read_mode())
data = import_file.read()
if not input_format.is_binary() and self.from_encoding:
data = force_text(data, self.from_encoding)
dataset = input_format.create_dataset(data)
result = resource.import_data(dataset, dry_run=False,
raise_errors=True)
# Add imported objects to LogEntry
ADDITION = 1
CHANGE = 2
DELETION = 3
logentry_map = {
RowResult.IMPORT_TYPE_NEW: ADDITION,
RowResult.IMPORT_TYPE_UPDATE: CHANGE,
RowResult.IMPORT_TYPE_DELETE: DELETION,
}
content_type_id=ContentType.objects.get_for_model(self.model).pk
'''
for row in result:
LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=content_type_id,
object_id=row.object_id,
object_repr=row.object_repr,
action_flag=logentry_map[row.import_type],
change_message="%s through import_export" % row.import_type,
)
'''
success_message = _('Import finished')
messages.success(self.request, success_message)
import_file.close()
url = reverse('%s_list' % (str(opts.app_label).lower()))
return HttpResponseRedirect(url)
the template import.html has the following code:
<h1>{% trans "Importar" %} {{ opts.app_label }}</h1>
{% if confirm_form %}
<form action="{% url "process_import" %}" method="POST">
{% csrf_token %}
{{ confirm_form.as_p }}
<p>
{% trans "Below is a preview of data to be imported. If you are satisfied with the results, click 'Confirm import'" %}
</p>
<div class="submit-row">
<input type="submit" class="btn" name="confirm" value="{% trans "Confirm import" %}">
</div>
</form>
{% else %}
<form action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form" enctype="multipart/form-data">
{% csrf_token %}
<p>
{% trans "This importer will import the following fields: " %}
{% for f in fields %}
{% if forloop.counter0 %}
,
{% endif %}
<tt>{{ f }}</tt>
{% endfor %}
</p>
<fieldset class="module aligned">
{% for field in form %}
<div class="form-row">
{{ field.errors }}
{{ field.label_tag }}
{{ field }}
{% if field.field.help_text %}
<p class="help">{{ field.field.help_text|safe }}</p>
{% endif %}
</div>
{% endfor %}
</fieldset>
<div class="submit-row">
<input type="submit" class="btn" value="{% trans "Submit" %}">
</div>
</form>
{% endif %}
{% if result %}
{% if result.has_errors %}
<h2>{% trans "Errors" %}</h2>
<ul>
{% for error in result.base_errors %}
<li>{{ error.error }}</li>
{% endfor %}
{% for line, errors in result.row_errors %}
{% for error in errors %}
<li>
{% trans "Line number" %}: {{ line }} - {{ error.error }}
<div class="traceback">{{ error.traceback|linebreaks }}</div>
</li>
{% endfor %}
{% endfor %}
</ul>
{% else %}
<h2>
{% trans "Preview" %}
</h2>
<table>
<thead>
<tr>
<th></th>
{% for field in fields %}
<th>{{ field }}</th>
{% endfor %}
</tr>
</thead>
{% for row in result.rows %}
<tr>
<td>
{% if row.import_type == 'new' %}
{% trans "New" %}
{% elif row.import_type == 'skip' %}
{% trans "Skipped" %}
{% elif row.import_type == 'delete' %}
{% trans "Delete" %}
{% elif row.import_type == 'update' %}
{% trans "Update" %}
{% endif %}
</td>
{% for field in row.diff %}
<td>
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
{% endif %}
{% endif %}
and the urls.py should contain:
#export
url(r'export/$', login_required(CountryExport.as_view()), name='country_export'),
#import
url(r'import/$', login_required(CountryImport.as_view()), name='country_import'),
url(r'process_import/$', login_required(CountryProcessImport.as_view()), name='process_import'),
I've done the exact same thing today. I tried to make the view fairly generic and allow values to be passed through in the URL so it is a little more complex than simply returning an XLS file.
But anyway, this was my approach;
class PlaceResource(resources.ModelResource):
class Meta:
model = Place
class ExportPlacesView(View):
model = Place
def get(self, request, *args, **kwargs):
client_name = str(self.client).replace(' ', '_').lower()
if 'query' in kwargs:
if kwargs['query'] == u'complete':
complete_runners = Place.objects.filter(
~models.Q(runner__completed_on=None),
console=self.console).values_list(
'client',
flat=True).distinct()
dataset = PlaceResource().export(
Place.objects.filter(
console=self.console, client=self.client,
runner__in=complete_runners
)
)
filename = '{}_completed_runners'.format(client_name)
else:
dataset = PlaceResource().export(Place.objects.filter(
console=self.console, client=self.client,
))
filename = '{}_Runners'.format(client_name)
export_type = kwargs['format']
_dataset_methods = {
'csv': dataset.csv,
'xls': dataset.xls
}
response = HttpResponse(
_dataset_methods[export_type], content_type=export_type
)
response[
'Content-Disposition'] = 'attachment; filename={filename}.{ext}'.format(
filename=filename,
ext=export_type
)
return response

dynamically connecting Django Q objects with AND and OR

I want users to be able to query my database via several different parameters (name, year, etc), dynamically add more fields, and join them with boolean operators; the net result would be something like "year = 1900 AND name = chicken AND location = San Francisco." I think I'm doing something wrong, since it's not returning anything, even when I try just one field with a value that I know matches some data (e.g., I can get objects back when I use .filter() from the Django shell). Anyone know how I can fix it?
Relevant view (ignore the sloppy indentation, I didn't want to go through and fix all of it, but it's right in my actual code):
class BaseSearchFormSet(BaseFormSet):
def clean(self):
if any(self.errors):
return self.errors
queries = []
valid_courses = ["appetizer","main","dessert"]
valid_period = re.compile(r'\d\d\d0-\d\d\d5|\d\d\d5-\d\d\d0')
valid_year = re.compile(r'\d{4}')
multi_rows = ["year","period","course"]
for x in xrange(0,self.total_form_count()):
form = self.forms[x]
query = form.cleaned_data.get("query")
row = form.cleaned_data.get("row")
if query in queries and row not in multi_rows:
raise forms.ValidationError("You're already searching for %s.")
queries.append(query)
if row == "course" and query.lower() not in valid_courses:
raise forms.ValidationError("%s is not a valid course option."%(form.cleaned_data["query"]))
if row == "period" and not re.match(valid_period,query):
raise forms.ValidationError("%s is not a properly formatted period. Valid five-year periods span either the first or second half of a decade. For example: 1910-1915, 1925-1930."%(form.cleaned_data["query"]))
if row == "year" and not re.match(valid_year,query):
raise forms.ValidationError("Please enter a four-digit year.")
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)
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})
models:
from django.db import models
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)
page=models.OneToOneField('MenuPage')
mk=models.CharField(max_length=10,unique=True)
formatted_price = property(format_price)
class Menu(models.Model):
def period(self):#adapted from http://stackoverflow.com/questions/2272149/round-to-5or-other-number-in-python
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:
return self.mk
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()
class MenuPage(models.Model):
mk=models.CharField(max_length=10,unique=True)
menu_id=models.OneToOneField("Menu",to_field='mk')
#objects=MenuPageManager()
class Dish(models.Model):
def __unicode__(self):
return self.name
full_name = models.TextField()
name=models.CharField(unique=True,max_length=255)
mk=models.CharField(max_length=10,unique=True)
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)
html of 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>
<p>Regular expression search follows MySQL regular expression syntax, as described here.</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 Sam Raker and Rachel Rakov
<br />
Data graciously provided by What's on the Menu?
</div>
{% endblock %}
Sorry, my original post was more than you needed. To clarify, all you should need to do is:
Q(year__icontains=year_input_variable) | Q(city__icontains=city_input_variable) & Q(name__icontains=name_input_variable)
Use & for and, | for or.
What I had posted earlier is for if a query contains multiple words, it would either check to see if all of the words matched using operator.and or if any of the words matched using operator.or.

Categories

Resources