Starting to add new functioning tests, beginning with dependency checker
parent
b2face2680
commit
7cb9dbbb2e
|
@ -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
|
||||
-----------------
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
||||
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))
|
||||
|
|
|
@ -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":
|
||||
|
|
Loading…
Reference in New Issue