From 7cb9dbbb2ec6e71bcaab704a39b2025355a6540f Mon Sep 17 00:00:00 2001 From: Griffin Caprio Date: Tue, 6 Jan 2015 16:45:49 -0600 Subject: [PATCH] Starting to add new functioning tests, beginning with dependency checker --- README.md | 2 +- questionnaire/dependency_checker.py | 126 +++++++++++ .../{test_urls.py => legacy_test_urls.py} | 0 questionnaire/legacy_tests.py | 191 ++++++++++++++++ questionnaire/tests.py | 214 +++--------------- questionnaire/views.py | 99 +------- 6 files changed, 354 insertions(+), 278 deletions(-) create mode 100644 questionnaire/dependency_checker.py rename questionnaire/{test_urls.py => legacy_test_urls.py} (100%) create mode 100644 questionnaire/legacy_tests.py diff --git a/README.md b/README.md index d1716c0..f93434f 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ The old versions are tagged as follows: * tag 2.0 - original updated trunk from Seantis version. * tag 2.5 - contains the original Seantis version & all PRs merged in as of 12/09/15. It's considered to be the backwards compatible version of the repository. -The new version is the current trunk and is dubbed v3.0. +The new version is the current trunk and is dubbed v3.0. v3.0 should be considered a new project & thus will contain backwards incompatible changes. When possible, we'll try and backport fixes to the v2.x branches, but it will not be a priority. About this Manual ----------------- diff --git a/questionnaire/dependency_checker.py b/questionnaire/dependency_checker.py new file mode 100644 index 0000000..d3d2cb7 --- /dev/null +++ b/questionnaire/dependency_checker.py @@ -0,0 +1,126 @@ +from questionnaire.models import Question, Answer +import logging + + +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("!"): + for actual_answer in actual_answer: + if actual_answer == '': + return False + if check_answer[1:].strip() == actual_answer.strip(): + return False + return True + return + + # 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 = expr + ",yes" + + check_questionnum, check_answer = expr.split(",", 1) + + # Get question to check + try: + check_question = Question.objects.get(number=check_questionnum, + 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_questionnum, False): + actual_answer = runinfo.get_cookie(check_questionnum) + else: + # retrieve from database + ansobj = Answer.objects.filter(question=check_question, + runid=runinfo.runid, subject=runinfo.subject) + if ansobj: + actual_answer = ansobj[0].split_answer()[0] + logging.warn("Put `store` in checks field for question %s" % check_questionnum) + 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'' + if type(actual_answer) == type(list()): + actual_answer = actual_answer[0] + + return check_actual_answers_against_expression(check_answer, actual_answer, check_question) diff --git a/questionnaire/test_urls.py b/questionnaire/legacy_test_urls.py similarity index 100% rename from questionnaire/test_urls.py rename to questionnaire/legacy_test_urls.py diff --git a/questionnaire/legacy_tests.py b/questionnaire/legacy_tests.py new file mode 100644 index 0000000..9a33abd --- /dev/null +++ b/questionnaire/legacy_tests.py @@ -0,0 +1,191 @@ +""" +Basic Test Suite for Questionnaire Application + +Unfortunately Django 1.0 only has TestCase and not TransactionTestCase +so we can't test that a submitted page with an error does not have any +answers submitted to the DB. +""" +from django.test import TestCase +from django.test.client import Client +from questionnaire.models import * +from datetime import datetime +import os + +class TypeTest(TestCase): + fixtures = ( 'testQuestions.yaml', ) + urls = 'questionnaire.test_urls' + + def setUp(self): + self.ansdict1 = { + 'questionset_id' : '1', + 'question_1' : 'Open Answer 1', + 'question_2' : 'Open Answer 2\r\nMultiline', + 'question_3' : 'yes', + 'question_4' : 'dontknow', + 'question_5' : 'yes', + 'question_5_comment' : 'this comment is required because of required-yes check', + 'question_6' : 'no', + 'question_6_comment' : 'this comment is required because of required-no check', + 'question_7' : '5', + 'question_8_unit' : 'week', + 'question_8' : '2', + } + self.ansdict2 = { + 'questionset_id' : '2', + 'question_9' : 'q9_choice1', # choice + 'question_10' : '_entry_', # choice-freeform + 'question_10_comment' : 'my freeform', + 'question_11_multiple_2' : 'q11_choice2', # choice-multiple + 'question_11_multiple_4' : 'q11_choice4', # choice-multiple + 'question_12_multiple_1' : 'q12_choice1',# choice-multiple-freeform + 'question_12_more_1' : 'blah', # choice-multiple-freeform + } + runinfo = self.runinfo = RunInfo.objects.get(runid='test:test') + self.runid = runinfo.runid + self.subject_id = runinfo.subject_id + + + def test010_redirect(self): + "Check redirection from generic questionnaire to questionset" + response = self.client.get('/q/test:test/') + self.assertEqual(response['Location'], 'http://testserver/q/test:test/1/') + + + def test020_get_questionset_1(self): + "Get first page of Questions" + response = self.client.get('/q/test:test/1/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.template[0].name, 'questionnaire/questionset.html') + + + def test030_language_setting(self): + "Set the language and confirm it is set in DB" + response = self.client.get('/q/test:test/1/', {"lang" : "en"}) + self.assertEqual(response.status_code, 302) + self.assertEqual(response['Location'], 'http://testserver/q/test:test/1/') + response = self.client.get('/q/test:test/1/') + assert "Don't Know" in response.content + self.assertEqual(response.status_code, 200) + runinfo = RunInfo.objects.get(runid='test:test') + self.assertEqual(runinfo.subject.language, 'en') + response = self.client.get('/q/test:test/1/', {"lang" : "de"}) + self.assertEqual(response.status_code, 302) + self.assertEqual(response['Location'], 'http://testserver/q/test:test/1/') + response = self.client.get('/q/test:test/1/') + assert "Weiss nicht" in response.content + self.assertEqual(response.status_code, 200) + runinfo = RunInfo.objects.get(runid='test:test') + self.assertEqual(runinfo.subject.language, 'de') + + + def test040_missing_question(self): + "Post questions with a mandatory field missing" + c = self.client + ansdict = self.ansdict1.copy() + del ansdict['question_3'] + response = c.post('/q/test:test/1/', ansdict) + self.assertEqual(response.status_code, 200) + errors = response.context[-1]['errors'] + self.assertEqual(len(errors), 1) and errors.has_key('3') + + + def test050_missing_question(self): + "Post questions with a mandatory field missing" + c = self.client + ansdict = self.ansdict1.copy() + del ansdict['question_5_comment'] + # first set language to english + response = self.client.get('/q/test:test/1/', {"lang" : "en"}) + response = c.post('/q/test:test/1/', ansdict) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context[-1]['errors']), 1) + + + def test060_successful_questionnaire(self): + "POST complete answers for QuestionSet 1" + c = self.client + ansdict1 = self.ansdict1 + runinfo = RunInfo.objects.get(runid='test:test') + runid = runinfo.random = runinfo.runid = '1real' + runinfo.save() + + response = c.get('/q/1real/1/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.template[0].name, 'questionnaire/questionset.html') + response = c.post('/q/1real/', ansdict1) + self.assertEqual(response.status_code, 302) + self.assertEqual(response['Location'], 'http://testserver/q/1real/2/') + "POST complete answers for QuestionSet 2" + c = self.client + + ansdict2 = self.ansdict2 + response = c.get('/q/1real/2/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.template[0].name, 'questionnaire/questionset.html') + response = c.post('/q/1real/', ansdict2) + self.assertEqual(response.status_code, 302) + self.assertEqual(response['Location'], 'http://testserver/') + + self.assertEqual(RunInfo.objects.filter(runid='1real').count(), 0) + + # TODO: The format of these answers seems very strange to me. It was + # simpler before I changed it to get the test to work. + # I'll have to revisit this once I figure out how this is meant to work + # for now it is more important to me that all tests pass + + dbvalues = { + '1' : u'["%s"]' % ansdict1['question_1'], + '2' : u'["%s"]' % ansdict1['question_2'], + '3' : u'["%s"]' % ansdict1['question_3'], + '4' : u'["%s"]' % ansdict1['question_4'], + '5' : u'["%s", ["%s"]]' % (ansdict1['question_5'], ansdict1['question_5_comment']), + '6' : u'["%s", ["%s"]]' % (ansdict1['question_6'], ansdict1['question_6_comment']), + '7' : u'[%s]' % ansdict1['question_7'], + '8' : u'%s; %s' % (ansdict1['question_8'], ansdict1['question_8_unit']), + '9' : u'["q9_choice1"]', + '10' : u'[["my freeform"]]', + '11' : u'["q11_choice2", "q11_choice4"]', + '12' : u'["q12_choice1", ["blah"]]', + } + for k, v in dbvalues.items(): + ans = Answer.objects.get(runid=runid, subject__id=self.subject_id, + question__number=k) + + v = v.replace('\r', '\\r').replace('\n', '\\n') + self.assertEqual(ans.answer, v) + + def test070_tags(self): + c = self.client + + # the first questionset in questionnaire 2 is always shown, + # but one of its 2 questions is tagged with testtag + with_tags = c.get('/q/test:withtags/1/') + + # so we'll get two questions shown if the run is tagged + self.assertEqual(with_tags.status_code, 200) + self.assertEqual(len(with_tags.context['qlist']), 2) + + # one question, if the run is not tagged + without_tags = c.get('/q/test:withouttags/1/') + + self.assertEqual(without_tags.status_code, 200) + self.assertEqual(len(without_tags.context['qlist']), 1) + + # the second questionset is only shown if the run is tagged + with_tags = c.get('/q/test:withtags/2/') + + self.assertEqual(with_tags.status_code, 200) + self.assertEqual(len(with_tags.context['qlist']), 1) + + # meaning it'll be skipped on the untagged run + without_tags = c.get('/q/test.withouttags/2/') + + self.assertEqual(without_tags.status_code, 302) # redirect + + # the progress values of the first questionset should reflect + # the fact that in one run there's only one questionset + with_tags = c.get('/q/test:withtags/1/') + without_tags = c.get('/q/test:withouttags/1/') + + self.assertEqual(with_tags.context['progress'], 50) + self.assertEqual(without_tags.context['progress'], 100) \ No newline at end of file diff --git a/questionnaire/tests.py b/questionnaire/tests.py index 9a33abd..4b95b8f 100644 --- a/questionnaire/tests.py +++ b/questionnaire/tests.py @@ -1,191 +1,47 @@ -""" -Basic Test Suite for Questionnaire Application - -Unfortunately Django 1.0 only has TestCase and not TransactionTestCase -so we can't test that a submitted page with an error does not have any -answers submitted to the DB. -""" from django.test import TestCase -from django.test.client import Client -from questionnaire.models import * -from datetime import datetime -import os -class TypeTest(TestCase): - fixtures = ( 'testQuestions.yaml', ) - urls = 'questionnaire.test_urls' - - def setUp(self): - self.ansdict1 = { - 'questionset_id' : '1', - 'question_1' : 'Open Answer 1', - 'question_2' : 'Open Answer 2\r\nMultiline', - 'question_3' : 'yes', - 'question_4' : 'dontknow', - 'question_5' : 'yes', - 'question_5_comment' : 'this comment is required because of required-yes check', - 'question_6' : 'no', - 'question_6_comment' : 'this comment is required because of required-no check', - 'question_7' : '5', - 'question_8_unit' : 'week', - 'question_8' : '2', - } - self.ansdict2 = { - 'questionset_id' : '2', - 'question_9' : 'q9_choice1', # choice - 'question_10' : '_entry_', # choice-freeform - 'question_10_comment' : 'my freeform', - 'question_11_multiple_2' : 'q11_choice2', # choice-multiple - 'question_11_multiple_4' : 'q11_choice4', # choice-multiple - 'question_12_multiple_1' : 'q12_choice1',# choice-multiple-freeform - 'question_12_more_1' : 'blah', # choice-multiple-freeform - } - runinfo = self.runinfo = RunInfo.objects.get(runid='test:test') - self.runid = runinfo.runid - self.subject_id = runinfo.subject_id +from dependency_checker import check_actual_answers_against_expression +from .models import Question - def test010_redirect(self): - "Check redirection from generic questionnaire to questionset" - response = self.client.get('/q/test:test/') - self.assertEqual(response['Location'], 'http://testserver/q/test:test/1/') +class QuestionSetTests(TestCase): + def test_dependencies_for_multiple_choice_question(self): + check_question = Question() + self.assertTrue(check_actual_answers_against_expression('3B', ['3B', '3E'], check_question)) + def test_dependencies_for_multiple_choice_question_false(self): + check_question = Question() + self.assertFalse(check_actual_answers_against_expression('3C', ['3B', '3E'], check_question)) - def test020_get_questionset_1(self): - "Get first page of Questions" - response = self.client.get('/q/test:test/1/') - self.assertEqual(response.status_code, 200) - self.assertEqual(response.template[0].name, 'questionnaire/questionset.html') + def test_dependencies_for_multiple_choice_question_negation(self): + check_question = Question() + self.assertTrue(check_actual_answers_against_expression('!3C', ['3B', '3E'], check_question)) + def test_dependencies_for_multiple_choice_question_negation_false(self): + check_question = Question() + self.assertFalse(check_actual_answers_against_expression('!3C', ['3C', '3E'], check_question)) - def test030_language_setting(self): - "Set the language and confirm it is set in DB" - response = self.client.get('/q/test:test/1/', {"lang" : "en"}) - self.assertEqual(response.status_code, 302) - self.assertEqual(response['Location'], 'http://testserver/q/test:test/1/') - response = self.client.get('/q/test:test/1/') - assert "Don't Know" in response.content - self.assertEqual(response.status_code, 200) - runinfo = RunInfo.objects.get(runid='test:test') - self.assertEqual(runinfo.subject.language, 'en') - response = self.client.get('/q/test:test/1/', {"lang" : "de"}) - self.assertEqual(response.status_code, 302) - self.assertEqual(response['Location'], 'http://testserver/q/test:test/1/') - response = self.client.get('/q/test:test/1/') - assert "Weiss nicht" in response.content - self.assertEqual(response.status_code, 200) - runinfo = RunInfo.objects.get(runid='test:test') - self.assertEqual(runinfo.subject.language, 'de') + def test_dependencies_for_single_choice_question(self): + check_question = Question() + self.assertTrue(check_actual_answers_against_expression('3B', '3B', check_question)) + self.assertFalse(check_actual_answers_against_expression('3C', '3B', check_question)) + self.assertTrue(check_actual_answers_against_expression('!3C', '3B', check_question)) + self.assertTrue(check_actual_answers_against_expression('!3C', '', check_question)) + def test_dependencies_for_numeric_checks(self): + check_question = Question() + self.assertTrue(check_actual_answers_against_expression('>5.6', '6', check_question)) + self.assertFalse(check_actual_answers_against_expression('>5.6', '3.6', check_question)) + self.assertFalse(check_actual_answers_against_expression('>5.6', '5.6', check_question)) - def test040_missing_question(self): - "Post questions with a mandatory field missing" - c = self.client - ansdict = self.ansdict1.copy() - del ansdict['question_3'] - response = c.post('/q/test:test/1/', ansdict) - self.assertEqual(response.status_code, 200) - errors = response.context[-1]['errors'] - self.assertEqual(len(errors), 1) and errors.has_key('3') + self.assertTrue(check_actual_answers_against_expression('>=5.6', '6', check_question)) + self.assertFalse(check_actual_answers_against_expression('>=5.6', '3.6', check_question)) + self.assertTrue(check_actual_answers_against_expression('>=5.6', '5.6', check_question)) + self.assertTrue(check_actual_answers_against_expression('<5.6', '4.6', check_question)) + self.assertFalse(check_actual_answers_against_expression('<5.6', '8.6', check_question)) + self.assertFalse(check_actual_answers_against_expression('<5.6', '5.6', check_question)) - def test050_missing_question(self): - "Post questions with a mandatory field missing" - c = self.client - ansdict = self.ansdict1.copy() - del ansdict['question_5_comment'] - # first set language to english - response = self.client.get('/q/test:test/1/', {"lang" : "en"}) - response = c.post('/q/test:test/1/', ansdict) - self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context[-1]['errors']), 1) - - - def test060_successful_questionnaire(self): - "POST complete answers for QuestionSet 1" - c = self.client - ansdict1 = self.ansdict1 - runinfo = RunInfo.objects.get(runid='test:test') - runid = runinfo.random = runinfo.runid = '1real' - runinfo.save() - - response = c.get('/q/1real/1/') - self.assertEqual(response.status_code, 200) - self.assertEqual(response.template[0].name, 'questionnaire/questionset.html') - response = c.post('/q/1real/', ansdict1) - self.assertEqual(response.status_code, 302) - self.assertEqual(response['Location'], 'http://testserver/q/1real/2/') - "POST complete answers for QuestionSet 2" - c = self.client - - ansdict2 = self.ansdict2 - response = c.get('/q/1real/2/') - self.assertEqual(response.status_code, 200) - self.assertEqual(response.template[0].name, 'questionnaire/questionset.html') - response = c.post('/q/1real/', ansdict2) - self.assertEqual(response.status_code, 302) - self.assertEqual(response['Location'], 'http://testserver/') - - self.assertEqual(RunInfo.objects.filter(runid='1real').count(), 0) - - # TODO: The format of these answers seems very strange to me. It was - # simpler before I changed it to get the test to work. - # I'll have to revisit this once I figure out how this is meant to work - # for now it is more important to me that all tests pass - - dbvalues = { - '1' : u'["%s"]' % ansdict1['question_1'], - '2' : u'["%s"]' % ansdict1['question_2'], - '3' : u'["%s"]' % ansdict1['question_3'], - '4' : u'["%s"]' % ansdict1['question_4'], - '5' : u'["%s", ["%s"]]' % (ansdict1['question_5'], ansdict1['question_5_comment']), - '6' : u'["%s", ["%s"]]' % (ansdict1['question_6'], ansdict1['question_6_comment']), - '7' : u'[%s]' % ansdict1['question_7'], - '8' : u'%s; %s' % (ansdict1['question_8'], ansdict1['question_8_unit']), - '9' : u'["q9_choice1"]', - '10' : u'[["my freeform"]]', - '11' : u'["q11_choice2", "q11_choice4"]', - '12' : u'["q12_choice1", ["blah"]]', - } - for k, v in dbvalues.items(): - ans = Answer.objects.get(runid=runid, subject__id=self.subject_id, - question__number=k) - - v = v.replace('\r', '\\r').replace('\n', '\\n') - self.assertEqual(ans.answer, v) - - def test070_tags(self): - c = self.client - - # the first questionset in questionnaire 2 is always shown, - # but one of its 2 questions is tagged with testtag - with_tags = c.get('/q/test:withtags/1/') - - # so we'll get two questions shown if the run is tagged - self.assertEqual(with_tags.status_code, 200) - self.assertEqual(len(with_tags.context['qlist']), 2) - - # one question, if the run is not tagged - without_tags = c.get('/q/test:withouttags/1/') - - self.assertEqual(without_tags.status_code, 200) - self.assertEqual(len(without_tags.context['qlist']), 1) - - # the second questionset is only shown if the run is tagged - with_tags = c.get('/q/test:withtags/2/') - - self.assertEqual(with_tags.status_code, 200) - self.assertEqual(len(with_tags.context['qlist']), 1) - - # meaning it'll be skipped on the untagged run - without_tags = c.get('/q/test.withouttags/2/') - - self.assertEqual(without_tags.status_code, 302) # redirect - - # the progress values of the first questionset should reflect - # the fact that in one run there's only one questionset - with_tags = c.get('/q/test:withtags/1/') - without_tags = c.get('/q/test:withouttags/1/') - - self.assertEqual(with_tags.context['progress'], 50) - self.assertEqual(without_tags.context['progress'], 100) \ No newline at end of file + self.assertTrue(check_actual_answers_against_expression('<=5.6', '3.6', check_question)) + self.assertFalse(check_actual_answers_against_expression('<=5.6', '9.6', check_question)) + self.assertTrue(check_actual_answers_against_expression('<=5.6', '5.6', check_question)) diff --git a/questionnaire/views.py b/questionnaire/views.py index 946ae4d..1a47526 100644 --- a/questionnaire/views.py +++ b/questionnaire/views.py @@ -20,6 +20,7 @@ from questionnaire.parsers import * from questionnaire.emails import _send_email, send_emails from questionnaire.utils import numal_sort, split_numal from questionnaire.request_cache import request_cache +from questionnaire.dependency_checker import dep_check from questionnaire import profiler import logging import random @@ -915,104 +916,6 @@ def has_tag(tag, runinfo): return tag in (t.strip() for t in runinfo.tags.split(',')) -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 - - 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: - # 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_questionnum, False): - actual_answer = runinfo.get_cookie(check_questionnum) - else: - # retrieve from database - ansobj = Answer.objects.filter(question=check_question, - runid=runinfo.runid, subject=runinfo.subject) - if ansobj: - actual_answer = ansobj[0].split_answer()[0] - logging.warn("Put `store` in checks field for question %s" % check_questionnum) - 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'' - if type(actual_answer) == type(list()): - actual_answer = actual_answer[0] - 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("!"): - if actual_answer == '': - return False - return check_answer[1:].strip() != actual_answer.strip() - return check_answer.strip() == actual_answer.strip() - - @permission_required("questionnaire.management") def send_email(request, runinfo_id): if request.method != "POST":