2016-04-26 18:38:32 +00:00
|
|
|
from .models import Question, Answer
|
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
|
|
def explode_answer_into_list(answers):
|
|
|
|
answer_list = []
|
|
|
|
|
|
|
|
for answer in answers:
|
|
|
|
if type(answer) == type(list()):
|
|
|
|
string_list = filter(lambda v: not isinstance(v, int), answer)
|
|
|
|
# remove all number values from list - this means choice-multiple-values's values
|
|
|
|
answer_list.extend(string_list)
|
|
|
|
else:
|
|
|
|
answer_list.append(answer)
|
|
|
|
|
|
|
|
return answer_list
|
|
|
|
|
|
|
|
|
|
|
|
def check_actual_answers_against_expression(check_answer, actual_answer, check_question):
|
|
|
|
# Numeric Value Expressions
|
|
|
|
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
|
|
|
|
|
|
|
|
# Convert answer to list if not already one
|
|
|
|
if type(actual_answer) != type(list()):
|
|
|
|
actual_answer = [actual_answer]
|
|
|
|
|
|
|
|
# Negative Value Expressions
|
|
|
|
if check_answer.startswith("!"):
|
|
|
|
if len(actual_answer) == 0:
|
|
|
|
return False
|
|
|
|
for actual_answer in actual_answer:
|
|
|
|
if actual_answer == '':
|
|
|
|
return False
|
|
|
|
if check_answer[1:].strip() == actual_answer.strip():
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
# Positive Value Expressions
|
|
|
|
for actual_answer in actual_answer:
|
|
|
|
if check_answer.strip() == actual_answer.strip():
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
"""
|
|
|
|
|
|
|
|
if hasattr(runinfo, 'questionset'):
|
|
|
|
questionnaire = runinfo.questionset.questionnaire
|
|
|
|
elif hasattr(runinfo, 'questionnaire'):
|
|
|
|
questionnaire = runinfo.questionnaire
|
|
|
|
else:
|
|
|
|
assert False
|
|
|
|
|
|
|
|
# Parse expression
|
|
|
|
if "," not in expr:
|
|
|
|
expr += ",yes"
|
|
|
|
|
|
|
|
check_question_number, check_answer = expr.split(",", 1)
|
|
|
|
|
|
|
|
# Get question to check
|
|
|
|
try:
|
|
|
|
check_question = Question.objects.get(number=check_question_number,
|
|
|
|
questionset__questionnaire=questionnaire)
|
|
|
|
except Question.DoesNotExist:
|
|
|
|
return False
|
|
|
|
|
|
|
|
# Parse & load actual answer(s) from user
|
|
|
|
if check_question in answerdict:
|
|
|
|
# test for membership in multiple choice questions
|
|
|
|
# FIXME: only checking answerdict
|
|
|
|
for k, v in answerdict[check_question].items():
|
|
|
|
if not k.startswith('multiple_'):
|
|
|
|
continue
|
|
|
|
if check_answer.startswith("!"):
|
|
|
|
if check_answer[1:].strip() == v.strip():
|
|
|
|
return False
|
|
|
|
elif check_answer.strip() == v.strip():
|
|
|
|
return True
|
|
|
|
actual_answer = answerdict[check_question].get('ANSWER', '')
|
|
|
|
elif hasattr(runinfo, 'get_cookie') and runinfo.get_cookie(check_question_number, False):
|
|
|
|
actual_answer = runinfo.get_cookie(check_question_number)
|
|
|
|
else:
|
|
|
|
# retrieve from database
|
|
|
|
answer_object = Answer.objects.filter(question=check_question,
|
2016-09-30 18:04:41 +00:00
|
|
|
run=runinfo.run,
|
2016-04-26 18:38:32 +00:00
|
|
|
subject=runinfo.subject)
|
|
|
|
if answer_object:
|
|
|
|
actual_answer = answer_object[0].split_answer()
|
|
|
|
logging.warn("Put `store` in checks field for question %s" % check_question_number)
|
|
|
|
else:
|
|
|
|
actual_answer = None
|
|
|
|
|
|
|
|
if not actual_answer:
|
|
|
|
if check_question.getcheckdict():
|
|
|
|
actual_answer = check_question.getcheckdict().get('default')
|
|
|
|
|
|
|
|
if actual_answer is None:
|
|
|
|
actual_answer = u''
|
|
|
|
|
|
|
|
# Convert freeform question type answers from a list of lists to just a single list.
|
|
|
|
# FIXME: Figure out why the hell freeform questions store their values as lists within lists.
|
|
|
|
#Don't shrink to one list if it isn't even a list though, otherwise we could get a string
|
|
|
|
#becoming a character array and falsely failing the comparison.
|
|
|
|
answer_list = actual_answer #might not actually be a list but meh
|
|
|
|
if type(actual_answer) == type(list()):
|
|
|
|
answer_list = explode_answer_into_list(actual_answer)
|
|
|
|
|
|
|
|
return check_actual_answers_against_expression(check_answer, answer_list, check_question)
|