fef-questionnaire/questionnaire/dependency_checker.py

148 lines
5.4 KiB
Python
Raw Normal View History

from questionnaire.models import Question, Answer
import logging
2015-01-07 00:53:58 +00:00
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)
2015-01-07 00:53:58 +00:00
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:
2015-04-15 12:38:30 +00:00
return False
for actual_answer in actual_answer:
if actual_answer == '':
2015-04-15 12:44:52 +00:00
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,
runid=runinfo.runid,
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.
2015-11-29 08:45:23 +00:00
#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)
2015-01-07 00:53:58 +00:00
return check_actual_answers_against_expression(check_answer, answer_list, check_question)