2009-05-17 11:34:55 +00:00
|
|
|
#!/usr/bin/python
|
|
|
|
# vim: set fileencoding=utf-8
|
|
|
|
|
|
|
|
from django.http import HttpResponse, Http404, \
|
|
|
|
HttpResponsePermanentRedirect, HttpResponseRedirect
|
|
|
|
from django.template import RequestContext, Context, Template, loader
|
|
|
|
from django.views.decorators.cache import cache_control
|
|
|
|
from django.core.urlresolvers import reverse
|
|
|
|
from django.contrib.auth.decorators import login_required
|
|
|
|
from django.shortcuts import render_to_response, get_object_or_404
|
|
|
|
from django.contrib.sites.models import Site
|
|
|
|
from django.db import transaction
|
|
|
|
from django.conf import settings
|
|
|
|
from models import *
|
|
|
|
from questionnaire import QuestionProcessors, Processors, AnswerException, \
|
|
|
|
questionset_done, questionnaire_done
|
|
|
|
from datetime import datetime
|
|
|
|
from django.utils import translation
|
|
|
|
import time, os, smtplib, rfc822
|
|
|
|
from parsers import *
|
|
|
|
from utils import numal_sort, split_numal, calc_alignment
|
|
|
|
from emails import send_emails, _send_email
|
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
|
|
def r2r(tpl, request, **contextdict):
|
|
|
|
"Shortcut to use RequestContext instead of Context in templates"
|
|
|
|
contextdict['request'] = request
|
|
|
|
return render_to_response(tpl, contextdict, context_instance = RequestContext(request))
|
|
|
|
|
|
|
|
|
|
|
|
def get_runinfo(random):
|
|
|
|
"Return the RunInfo entry with the provided random key"
|
2009-06-15 16:07:14 +00:00
|
|
|
res = RunInfo.objects.filter(random=random.lower())
|
2009-05-17 11:34:55 +00:00
|
|
|
if res:
|
|
|
|
return res[0]
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def get_question(number, questionnaire):
|
|
|
|
"Return the specified Question (by number) from the specified Questionnaire"
|
|
|
|
res = Question.objects.filter(number=number, questionset__questionnaire=questionnaire)
|
|
|
|
if res:
|
|
|
|
return res[0]
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def delete_answer(question, subject, runid):
|
|
|
|
"Delete the specified question/subject/runid combination from the Answer table"
|
|
|
|
Answer.objects.filter(subject=subject, runid=runid, question=question).delete()
|
|
|
|
|
|
|
|
|
|
|
|
def add_answer(runinfo, question, answer_dict):
|
|
|
|
"""
|
|
|
|
Add an Answer to a Question for RunInfo, given the relevant form input
|
|
|
|
|
|
|
|
answer_dict contains the POST'd elements for this question, minus the
|
|
|
|
question_{number} prefix. The question_{number} form value is accessible
|
|
|
|
with the ANSWER key.
|
|
|
|
"""
|
|
|
|
answer = Answer()
|
|
|
|
answer.question = question
|
|
|
|
answer.subject = runinfo.subject
|
|
|
|
answer.runid = runinfo.runid
|
|
|
|
|
|
|
|
type = question.get_type()
|
|
|
|
|
|
|
|
if "ANSWER" not in answer_dict:
|
|
|
|
answer_dict['ANSWER'] = None
|
|
|
|
|
|
|
|
if type in Processors:
|
|
|
|
answer.answer = Processors[type](question, answer_dict) or ''
|
|
|
|
else:
|
|
|
|
raise AnswerException("No Processor defined for question type %s" % type)
|
|
|
|
|
|
|
|
# first, delete all existing answers to this question for this particular user+run
|
|
|
|
delete_answer(question, runinfo.subject, runinfo.runid)
|
|
|
|
# then save the new answer to the database
|
|
|
|
answer.save()
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def questionset_satisfies_checks(questionset, runinfo):
|
|
|
|
"Return True is RunInfo satisfied checks for the specified QuestionSet"
|
|
|
|
checks = parse_checks(questionset.checks)
|
|
|
|
for check, value in checks.items():
|
|
|
|
if check == 'maleonly' and runinfo.subject.gender != 'male':
|
|
|
|
return False
|
|
|
|
if check == 'femaleonly' and runinfo.subject.gender != 'female':
|
|
|
|
return False
|
|
|
|
if check == 'shownif' and value and value.strip():
|
|
|
|
depparser = BooleanParser(dep_check, runinfo, {})
|
|
|
|
res = depparser.parse(value)
|
|
|
|
if not res:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def redirect_to_qs(runinfo):
|
|
|
|
"Redirect to the correct and current questionset URL for this RunInfo"
|
|
|
|
url = reverse("questionset",
|
|
|
|
args=[ runinfo.random, runinfo.questionset.sortid ])
|
|
|
|
return HttpResponseRedirect(url)
|
|
|
|
|
|
|
|
|
|
|
|
@transaction.commit_on_success
|
|
|
|
def questionnaire(request, runcode=None, qs=None):
|
|
|
|
"""
|
|
|
|
Process submitted answers (if present) and redirect to next page
|
|
|
|
|
|
|
|
If this is a POST request, parse the submitted data in order to store
|
|
|
|
all the submitted answers. Then return to the next questionset or
|
|
|
|
return a completed response.
|
|
|
|
|
|
|
|
If this isn't a POST request, redirect to the main page.
|
|
|
|
|
|
|
|
We only commit on success, to maintain consistency. We also specifically
|
|
|
|
rollback if there were errors processing the answers for this questionset.
|
|
|
|
"""
|
|
|
|
|
|
|
|
# if runcode provided as query string, redirect to the proper page
|
|
|
|
if not runcode and request.GET.has_key("runcode"):
|
|
|
|
transaction.commit()
|
|
|
|
return HttpResponseRedirect(
|
|
|
|
reverse("questionnaire",
|
|
|
|
args=[request.GET['runcode']]))
|
|
|
|
|
|
|
|
runinfo = get_runinfo(runcode)
|
|
|
|
|
|
|
|
if not runinfo:
|
|
|
|
transaction.commit()
|
|
|
|
return HttpResponseRedirect('/')
|
|
|
|
|
2009-06-15 17:05:59 +00:00
|
|
|
if not qs:
|
2009-05-17 11:34:55 +00:00
|
|
|
# Only change the language to the subjects choice for the initial
|
|
|
|
# questionnaire page (may be a direct link from an email)
|
|
|
|
if hasattr(request, 'session'):
|
|
|
|
request.session['django_language'] = runinfo.subject.language
|
|
|
|
translation.activate(runinfo.subject.language)
|
|
|
|
|
2009-06-15 17:05:59 +00:00
|
|
|
if 'lang' in request.GET:
|
|
|
|
return set_language(request, runinfo, request.path)
|
|
|
|
|
2009-05-17 11:34:55 +00:00
|
|
|
# --------------------------------
|
|
|
|
# --- Handle non-POST requests ---
|
|
|
|
# --------------------------------
|
|
|
|
|
|
|
|
if request.method != "POST":
|
|
|
|
if qs is not None:
|
|
|
|
qs = get_object_or_404(QuestionSet, sortid=qs, questionnaire=runinfo.questionset.questionnaire)
|
|
|
|
if runinfo.random.startswith('test:'):
|
|
|
|
pass # ok for testing
|
|
|
|
elif qs.sortid > runinfo.questionset.sortid:
|
|
|
|
# you may jump back, but not forwards
|
|
|
|
return redirect_to_qs(runinfo)
|
|
|
|
runinfo.questionset = qs
|
|
|
|
runinfo.save()
|
|
|
|
transaction.commit()
|
|
|
|
# no questionset id in URL, so redirect to the correct URL
|
|
|
|
if qs is None:
|
|
|
|
return redirect_to_qs(runinfo)
|
|
|
|
return show_questionnaire(request, runinfo)
|
|
|
|
|
|
|
|
# -------------------------------------
|
|
|
|
# --- Process POST with QuestionSet ---
|
|
|
|
# -------------------------------------
|
|
|
|
|
|
|
|
# if the submitted page is different to what runinfo says, update runinfo
|
|
|
|
# XXX - do we really want this?
|
|
|
|
qs = request.POST.get('questionset_id', None)
|
|
|
|
try:
|
|
|
|
qsobj = QuestionSet.objects.filter(pk=qs)[0]
|
|
|
|
if qsobj.questionnaire == runinfo.questionset.questionnaire:
|
|
|
|
if runinfo.questionset != qsobj:
|
|
|
|
runinfo.questionset = qsobj
|
|
|
|
runinfo.save()
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
|
|
|
questionnaire = runinfo.questionset.questionnaire
|
|
|
|
questionset = runinfo.questionset
|
|
|
|
|
|
|
|
# to confirm that we have the correct answers
|
|
|
|
expected = questionset.questions()
|
|
|
|
|
|
|
|
items = request.POST.items()
|
|
|
|
extra = {} # question_object => { "ANSWER" : "123", ... }
|
|
|
|
|
|
|
|
# this will ensure that each question will be processed, even if we did not receive
|
|
|
|
# any fields for it. Also works to ensure the user doesn't add extra fields in
|
|
|
|
for x in expected:
|
|
|
|
items.append( (u'question_%s_Trigger953' % x.number, None) )
|
|
|
|
|
|
|
|
# generate the answer_dict for each question, and place in extra
|
|
|
|
for item in items:
|
|
|
|
key, value = item[0], item[1]
|
|
|
|
if key.startswith('question_'):
|
|
|
|
answer = key.split("_", 2)
|
|
|
|
question = get_question(answer[1], questionnaire)
|
|
|
|
if not question:
|
|
|
|
logging.warn("Unknown question when processing: %s" % answer[1])
|
|
|
|
continue
|
|
|
|
extra[question] = ans = extra.get(question, {})
|
|
|
|
if(len(answer) == 2):
|
|
|
|
ans['ANSWER'] = value
|
|
|
|
elif(len(answer) == 3):
|
|
|
|
ans[answer[2]] = value
|
|
|
|
else:
|
|
|
|
logging.warn("Poorly formed form element name: %r" % answer)
|
|
|
|
continue
|
|
|
|
extra[question] = ans
|
|
|
|
|
|
|
|
errors = {}
|
|
|
|
for question, ans in extra.items():
|
|
|
|
if u"Trigger953" not in ans:
|
|
|
|
logging.warn("User attempted to insert extra question (or it's a bug)")
|
|
|
|
continue
|
|
|
|
try:
|
|
|
|
cd = question.getcheckdict()
|
|
|
|
# requiredif is the new way
|
|
|
|
depon = cd.get('requiredif',None) or cd.get('dependent',None)
|
|
|
|
if depon:
|
|
|
|
depparser = BooleanParser(dep_check, runinfo, extra)
|
|
|
|
if not depparser.parse(depon):
|
|
|
|
# if check is not the same as answer, then we don't care
|
|
|
|
# about this question plus we should delete it from the DB
|
|
|
|
delete_answer(question, runinfo.subject, runinfo.runid)
|
|
|
|
if cd.get('store', False):
|
|
|
|
runinfo.set_cookie(question.number, None)
|
|
|
|
continue
|
|
|
|
add_answer(runinfo, question, ans)
|
|
|
|
if cd.get('store', False):
|
|
|
|
runinfo.set_cookie(question.number, ans['ANSWER'])
|
|
|
|
except AnswerException, e:
|
|
|
|
errors[question.number] = e
|
|
|
|
except Exception:
|
|
|
|
logging.exception("Unexpected Exception")
|
|
|
|
transaction.rollback()
|
|
|
|
raise
|
|
|
|
|
|
|
|
if len(errors) > 0:
|
|
|
|
res = show_questionnaire(request, runinfo, errors=errors)
|
|
|
|
transaction.rollback()
|
|
|
|
return res
|
|
|
|
|
|
|
|
questionset_done.send(sender=None,runinfo=runinfo,questionset=questionset)
|
|
|
|
|
|
|
|
next = questionset.next()
|
|
|
|
while next and not questionset_satisfies_checks(next, runinfo):
|
|
|
|
next = next.next()
|
|
|
|
runinfo.questionset = next
|
|
|
|
runinfo.save()
|
|
|
|
|
|
|
|
if next is None: # we are finished
|
|
|
|
hist = RunInfoHistory()
|
|
|
|
hist.subject = runinfo.subject
|
|
|
|
hist.runid = runinfo.runid
|
|
|
|
hist.completed = datetime.now()
|
|
|
|
hist.save()
|
|
|
|
|
|
|
|
questionnaire_done.send(sender=None, runinfo=runinfo,
|
|
|
|
questionnaire=questionnaire)
|
|
|
|
|
|
|
|
redirect_url = questionnaire.redirect_url
|
|
|
|
for x,y in (('$LANG', translation.get_language()),
|
|
|
|
('$SUBJECTID', runinfo.subject.id),
|
|
|
|
('$RUNID', runinfo.runid),):
|
|
|
|
redirect_url = redirect_url.replace(x, str(y))
|
|
|
|
|
|
|
|
if runinfo.runid in ('12345', '54321') \
|
|
|
|
or runinfo.runid.startswith('test:'):
|
|
|
|
runinfo.questionset = QuestionSet.objects.filter(questionnaire=questionnaire).order_by('sortid')[0]
|
|
|
|
runinfo.save()
|
|
|
|
else:
|
|
|
|
runinfo.delete()
|
|
|
|
transaction.commit()
|
|
|
|
if redirect_url:
|
|
|
|
return HttpResponseRedirect(redirect_url)
|
|
|
|
return r2r("questionnaire/complete.$LANG.html", request)
|
|
|
|
|
|
|
|
transaction.commit()
|
|
|
|
return redirect_to_qs(runinfo)
|
|
|
|
|
|
|
|
|
|
|
|
def get_progress(percent):
|
|
|
|
"Based on a percentage as a float, calculate percentage needed for CSS progress bar"
|
|
|
|
if int(percent) >= 1:
|
|
|
|
return 100, "1"
|
|
|
|
pc = "-%s" % (120 - int(percent * 120) + 1)
|
|
|
|
return (int(percent * 100), pc)
|
|
|
|
|
|
|
|
|
|
|
|
def show_questionnaire(request, runinfo, errors={}):
|
|
|
|
"""
|
|
|
|
Return the QuestionSet template
|
|
|
|
|
|
|
|
Also add the javascript dependency code.
|
|
|
|
"""
|
|
|
|
questions = runinfo.questionset.questions()
|
|
|
|
total = len(runinfo.questionset.questionnaire.questionsets())
|
|
|
|
|
|
|
|
qlist = []
|
|
|
|
jsinclude = [] # js files to include
|
|
|
|
cssinclude = [] # css files to include
|
|
|
|
jstriggers = []
|
|
|
|
qvalues = {}
|
|
|
|
alignment=4
|
|
|
|
for question in questions:
|
|
|
|
Type = question.get_type()
|
|
|
|
_qnum, _qalpha = split_numal(question.number)
|
|
|
|
if _qalpha:
|
|
|
|
_qalpha_class = ord(_qalpha[-1]) % 2 and 'odd' or 'even'
|
|
|
|
qdict = {
|
|
|
|
'template' : 'questionnaire/%s.html' % (Type),
|
|
|
|
'qnum' : _qnum,
|
|
|
|
'qalpha' : _qalpha,
|
|
|
|
'qtype' : Type,
|
|
|
|
'qnum_class' : (_qnum % 2 == 0) and " qeven" or " qodd",
|
|
|
|
'qalpha_class' : _qalpha and (ord(_qalpha[-1]) % 2 \
|
|
|
|
and ' alodd' or ' aleven') or '',
|
|
|
|
}
|
|
|
|
|
|
|
|
if not question.newline():
|
|
|
|
alignment = max(alignment, calc_alignment(question.text))
|
|
|
|
|
|
|
|
# add javascript dependency checks
|
|
|
|
cd = question.getcheckdict()
|
|
|
|
depon = cd.get('requiredif',None) or cd.get('dependent',None)
|
|
|
|
if depon:
|
|
|
|
# extra args to BooleanParser are not required for toString
|
|
|
|
parser = BooleanParser(dep_check)
|
|
|
|
qdict['checkstring'] = ' checks="%s"' % parser.toString(depon)
|
|
|
|
jstriggers.append('qc_%s' % question.number)
|
|
|
|
if 'default' in cd:
|
|
|
|
qvalues[question.number] = cd['default']
|
|
|
|
if Type in QuestionProcessors:
|
|
|
|
qdict.update(QuestionProcessors[Type](request, question))
|
|
|
|
if 'alignment' in qdict:
|
|
|
|
alignment = max(alignment, qdict['alignment'])
|
|
|
|
if 'jsinclude' in qdict:
|
|
|
|
if qdict['jsinclude'] not in jsinclude:
|
|
|
|
jsinclude.extend(qdict['jsinclude'])
|
|
|
|
if 'cssinclude' in qdict:
|
|
|
|
if qdict['cssinclude'] not in cssinclude:
|
|
|
|
cssinclude.extend(qdict['jsinclude'])
|
|
|
|
if 'jstriggers' in qdict:
|
|
|
|
jstriggers.extend(qdict['jstriggers'])
|
|
|
|
if 'qvalue' in qdict:
|
|
|
|
qvalues[question.number] = qdict['qvalue']
|
|
|
|
qlist.append( (question, qdict) )
|
|
|
|
|
|
|
|
progress = None
|
|
|
|
if runinfo.questionset.sortid != 0:
|
|
|
|
progress = get_progress(runinfo.questionset.sortid / float(total))
|
|
|
|
|
|
|
|
# initialize qvalues
|
|
|
|
for k,v in runinfo.get_cookiedict().items():
|
|
|
|
qvalues[k] = v
|
|
|
|
if request.POST:
|
|
|
|
for k,v in request.POST.items():
|
|
|
|
if k.startswith("question_"):
|
|
|
|
s = k.split("_")
|
|
|
|
if len(s) == 2:
|
|
|
|
qvalues[s[1]] = v
|
|
|
|
|
|
|
|
r = r2r("questionnaire/questionset.html", request,
|
|
|
|
questionset=runinfo.questionset,
|
|
|
|
runinfo=runinfo,
|
|
|
|
errors=errors,
|
|
|
|
qlist=qlist,
|
|
|
|
progress=progress,
|
|
|
|
triggers=jstriggers,
|
|
|
|
qvalues=qvalues,
|
|
|
|
alignment=alignment,
|
|
|
|
jsinclude=jsinclude,
|
|
|
|
cssinclude=cssinclude)
|
|
|
|
r['Cache-Control'] = 'no-cache'
|
2009-06-15 17:05:59 +00:00
|
|
|
r['Expires'] = "Thu, 24 Jan 1980 00:00:00 GMT"
|
2009-05-17 11:34:55 +00:00
|
|
|
return r
|
|
|
|
|
|
|
|
|
2009-06-15 17:05:59 +00:00
|
|
|
def set_language(request, runinfo=None, next=None):
|
|
|
|
"""
|
|
|
|
Change the language, save it to runinfo if provided, and
|
|
|
|
redirect to the provided URL (or the last URL).
|
|
|
|
Can also be used by a url handler, w/o runinfo & next.
|
|
|
|
"""
|
|
|
|
if not next:
|
|
|
|
next = request.REQUEST.get('next', None)
|
|
|
|
if not next:
|
|
|
|
next = request.META.get('HTTP_REFERER', None)
|
|
|
|
if not next:
|
|
|
|
next = '/'
|
|
|
|
response = HttpResponseRedirect(next)
|
|
|
|
response['Expires'] = "Thu, 24 Jan 1980 00:00:00 GMT"
|
|
|
|
if request.method == 'GET':
|
|
|
|
lang_code = request.GET.get('lang', None)
|
|
|
|
if lang_code and translation.check_for_language(lang_code):
|
|
|
|
if hasattr(request, 'session'):
|
|
|
|
request.session['django_language'] = lang_code
|
|
|
|
else:
|
|
|
|
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang_code)
|
|
|
|
if runinfo:
|
|
|
|
runinfo.subject.language = lang_code
|
|
|
|
runinfo.subject.save()
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
2009-05-17 11:34:55 +00:00
|
|
|
@login_required
|
|
|
|
def export_csv(request, qid): # questionnaire_id
|
|
|
|
"""
|
|
|
|
For a given questionnaire id, generaete a CSV containing all the
|
|
|
|
answers for all subjects.
|
|
|
|
"""
|
|
|
|
import tempfile, csv
|
|
|
|
from django.core.servers.basehttp import FileWrapper
|
|
|
|
|
|
|
|
fd = tempfile.TemporaryFile()
|
|
|
|
qid = int(qid)
|
|
|
|
columns = [x[0] for x in Question.objects.filter(questionset__questionnaire__id = qid).distinct('number').values_list('number')]
|
|
|
|
columns.sort(numal_sort)
|
|
|
|
columns.insert(0,u'subject')
|
|
|
|
columns.insert(1,u'runid')
|
|
|
|
writer = csv.DictWriter(fd, columns, restval='--')
|
|
|
|
coldict = {}
|
|
|
|
for col in columns:
|
|
|
|
coldict[col] = col
|
|
|
|
writer.writerows([coldict,])
|
|
|
|
answers = Answer.objects.filter(question__questionset__questionnaire__id = qid).order_by('subject', 'runid', 'question__number',)
|
|
|
|
if not answers:
|
|
|
|
raise Exception, "EMPTY!" # FIXME
|
|
|
|
|
|
|
|
runid = answers[0].runid
|
|
|
|
subject = answers[0].subject
|
|
|
|
d = { u'subject' : "%s/%s" % (subject.id, subject.state), u'runid' : runid }
|
|
|
|
for answer in answers:
|
|
|
|
if answer.runid != runid or answer.subject != subject:
|
|
|
|
writer.writerows([d,])
|
|
|
|
runid = answer.runid
|
|
|
|
subject = answer.subject
|
|
|
|
d = { u'subject' : "%s/%s" % (subject.id, subject.state), u'runid' : runid }
|
|
|
|
d[answer.question.number] = answer.answer.encode('utf-8')
|
|
|
|
# and don't forget about the last one
|
|
|
|
if d:
|
|
|
|
writer.writerows([d,])
|
|
|
|
response = HttpResponse(FileWrapper(fd), mimetype="text/csv")
|
|
|
|
response['Content-Length'] = fd.tell()
|
|
|
|
response['Content-Disposition'] = 'attachment; filename="export-%s.csv"' % qid
|
|
|
|
fd.seek(0)
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
|
def dep_check(expr, runinfo, answerdict):
|
|
|
|
"""
|
|
|
|
Given a comma separated question number and expression, determine if the
|
|
|
|
provided answer to the question number satisfies the expression.
|
|
|
|
|
|
|
|
If the expression starts with >, >=, <, or <=, compare the rest of
|
|
|
|
the expression numerically and return False if it's not able to be
|
|
|
|
converted to an integer.
|
|
|
|
|
|
|
|
If the expression starts with !, return true if the rest of the expression
|
|
|
|
does not match the answer.
|
|
|
|
|
|
|
|
Otherwise return true if the expression matches the answer.
|
|
|
|
|
|
|
|
If there is no comma and only a question number, it checks if the answer
|
|
|
|
is "yes"
|
|
|
|
|
|
|
|
When looking up the answer, it first checks if it's in the answerdict,
|
|
|
|
then it checks runinfo's cookies, then it does a database lookup to find
|
|
|
|
the answer.
|
|
|
|
|
|
|
|
The use of the comma separator is purely historical.
|
|
|
|
"""
|
|
|
|
questionnaire = runinfo.questionset.questionnaire
|
|
|
|
if "," not in expr:
|
|
|
|
expr = expr + ",yes"
|
|
|
|
check_questionnum, check_answer = expr.split(",",1)
|
|
|
|
try:
|
|
|
|
check_question = Question.objects.get(number=check_questionnum,
|
|
|
|
questionset__questionnaire = questionnaire)
|
|
|
|
except Question.DoesNotExist:
|
|
|
|
return False
|
|
|
|
if check_question in answerdict:
|
|
|
|
actual_answer = answerdict[check_question].get('ANSWER', '')
|
|
|
|
elif runinfo.get_cookie(check_questionnum, False):
|
|
|
|
actual_answer = runinfo.get_cookie(check_questionnum)
|
|
|
|
else:
|
|
|
|
# retrieve from database
|
|
|
|
logging.warn("Put `store` in checks field for question %s" \
|
|
|
|
% check_questionnum)
|
|
|
|
ansobj = Answer.objects.filter(question=check_question,
|
|
|
|
runid=runinfo.runid, subject=runinfo.subject)
|
|
|
|
if ansobj:
|
|
|
|
actual_answer = ansobj[0].answer.split(";")[0]
|
|
|
|
else:
|
|
|
|
actual_answer = None
|
|
|
|
if actual_answer is None:
|
|
|
|
actual_answer = u''
|
|
|
|
if check_answer[0:1] in "<>":
|
|
|
|
try:
|
|
|
|
actual_answer = float(actual_answer)
|
|
|
|
if check_answer[1:2] == "=":
|
|
|
|
check_value = float(check_answer[2:])
|
|
|
|
else:
|
|
|
|
check_value = float(check_answer[1:])
|
|
|
|
except:
|
|
|
|
logging.error("ERROR: must use numeric values with < <= => > checks (%r)" % check_question)
|
|
|
|
return False
|
|
|
|
if check_answer.startswith("<="):
|
|
|
|
return actual_answer <= check_value
|
|
|
|
if check_answer.startswith(">="):
|
|
|
|
return actual_answer >= check_value
|
|
|
|
if check_answer.startswith("<"):
|
|
|
|
return actual_answer < check_value
|
|
|
|
if check_answer.startswith(">"):
|
|
|
|
return actual_answer > check_value
|
|
|
|
if check_answer.startswith("!"):
|
|
|
|
return check_answer[1:].strip() != actual_answer.strip()
|
|
|
|
return check_answer.strip() == actual_answer.strip()
|
|
|
|
|
|
|
|
@login_required
|
|
|
|
def send_email(request, runinfo_id):
|
|
|
|
if request.method != "POST":
|
|
|
|
return HttpResponse("This page MUST be called as a POST request.")
|
|
|
|
runinfo = get_object_or_404(RunInfo, pk=int(runinfo_id))
|
|
|
|
successful = _send_email(runinfo)
|
|
|
|
return r2r("emailsent.html", request, runinfo=runinfo, successful=successful)
|