Permalink
Browse files

brought the branch up to date with master

as it was originally branched off an out of date master branch
  • Loading branch information...
2 parents aea0aec + 01df4f5 commit 410e0c38ed65383dee87347fa81d6ff4f60b0020 @ctrlspc ctrlspc committed Sep 21, 2012
Showing with 240 additions and 19 deletions.
  1. +53 −5 questionnaire/models.py
  2. +91 −6 questionnaire/tests/models.py
  3. +78 −0 questionnaire/tests/views.py
  4. +7 −1 questionnaire/urls.py
  5. +11 −7 questionnaire/views.py
View
@@ -141,12 +141,39 @@ class Meta():
name = models.CharField('questiongroupname',max_length=255,unique=True)
questions = models.ManyToManyField(Question, through = 'Question_order')
+ #context fields
+ _context = None
+
def get_ordered_questions(self):
'''
@return: questions in question group ordered by order_info
'''
return [order.question for order in Question_order.objects.filter(questiongroup=self).order_by('order_info')]
+
+ def set_context(self, answer_set):
+ '''
+ A Question group can be assigned to many Questionnaires. And this questionnaire can be taken by many
+ Users. Therefore there will be many different combinations of questionnaire, user and answerset
+ associated with any question group.
+ Sometimes you will want to see the group in a specific context, and the best way to do this
+ is to associate an instance to a singel answer set this will give you access the contextualised
+ questionnaier, its user and the answers to its questions
+ This is not saved into the database or persisted in any other way, it is on an instance basis
+ '''
+
+ if not isinstance(answer_set, AnswerSet) :
+ raise AttributeError
+
+ self._context = answer_set
+
+ def clear_context(self):
+ '''
+ This allows you to clears the context fields for this instance.
+ '''
+ self._context = None
+
+
def __unicode__(self):
return self.name
@@ -166,7 +193,7 @@ def get_ordered_groups(self):
@return: the questiongroups in a questionnaire order by the order_info
'''
- return QuestionGroup_order.objects.filter(questionnaire=self).order_by('order_info')
+ return [order.questiongroup for order in QuestionGroup_order.objects.filter(questionnaire=self).order_by('order_info')]
def get_group_for_index(self, index):
'''
@@ -175,8 +202,8 @@ def get_group_for_index(self, index):
If there is not a group at this index in the ordered_groups then an index error will be thrown.
'''
- ordered_groups = [order_info.questiongroup for order_info in self.get_ordered_groups()]
- return (ordered_groups[index], (len(ordered_groups) - index) -1)
+ ordered_groups = self.get_ordered_groups()
+ return (self.get_ordered_groups()[index], (len(ordered_groups) - index) -1)
def add_question_group(self, questiongroup):
'''
@@ -247,7 +274,7 @@ def get_latest_question_answers(self):
have more than one QuestionAnswer for each question in a given answer set).
'''
return [record.question_answer for record in LatestQuestionAnswer.objects.filter(answer_set=self)]
-
+
def get_latest_question_answer_in_order(self):
'''
This function will return a list of QuestionAnswer objects in th same order that the questions are defined in
@@ -264,7 +291,28 @@ def get_latest_question_answer_in_order(self):
ordered_answers.append(answer_dict[question])
return ordered_answers
-
+
+ def is_complete(self):
+ '''
+ This function will return True is there is an answer for each of the
+ questions defined in the questiongroup. Otherwise it will return False
+ '''
+
+ answers = self.get_latest_question_answers()
+ questions = self.questiongroup.get_ordered_questions()
+
+ #get a list of the answered questions
+ answered_questions = []
+ for answer in answers:
+ answered_questions.append(answer.question)
+
+ for question in questions:
+ if question not in answered_questions:
+ return False
+
+ return True
+
+
class QuestionAnswer(models.Model):
'''
This model stores questions, answers and related answer_set
@@ -195,7 +195,48 @@ def test_get_ordered_questions(self):
self.assertEqual(questions[0].label, question_order1.question.label)
self.assertEqual(questions[1].label, question_order2.question.label)
self.assertEqual(questions[2].label, question_order3.question.label)
-
+
+ def test_set_context(self):
+ '''
+ If you pass in questionnaire and user objects, this function will set and
+ _user_context respectively with these values. If you pass in anything else
+ then an AttributeError will be thrown
+ '''
+ test_user = User.objects.create_user('test', 'test@test.com', 'password')
+ test_group = QuestionGroup.objects.get(pk=1)
+ test_questionnaire = Questionnaire.objects.get(id=1)
+ test_answer_set = AnswerSet.objects.create(user=test_user,
+ questionnaire=test_questionnaire,
+ questiongroup=test_group)
+
+ self.assertIsNone(test_group._context)#should start out empty
+
+ self.assertRaises(AttributeError, test_group.set_context, 'not an answerset')
+
+ test_group.set_context(test_answer_set)
+ self.assertEqual(test_group._context, test_answer_set)
+
+ def test_clear_questionnaire_context(self):
+ '''
+ This will set _questionnaire_context to None
+ '''
+ test_user = User.objects.create_user('test', 'test@test.com', 'password')
+ test_group = QuestionGroup.objects.get(pk=1)
+ test_questionnaire = Questionnaire.objects.get(id=1)
+ test_answer_set = AnswerSet.objects.create(user=test_user,
+ questionnaire=test_questionnaire,
+ questiongroup=test_group)
+
+ self.assertIsNone(test_group._context)#should start out empty
+
+ test_group.clear_context()
+ self.assertIsNone(test_group._context)
+
+ test_group._context = test_answer_set
+ test_group.clear_context()
+ self.assertIsNone(test_group._context)
+
+
class QuestionnaireTestCase(TestCase):
fixtures = ['test_questionnaire_fixtures_formodels.json']
@@ -227,10 +268,10 @@ def test_get_ordered_question_group(self):
'''
questionnaire_test = Questionnaire.objects.get(pk=1)
question_group = questionnaire_test.get_ordered_groups()
- question_group1 = QuestionGroup_order.objects.get(pk=1)
- question_group2 = QuestionGroup_order.objects.get(pk=2)
- self.assertEqual(question_group[0].questiongroup.name, question_group1.questiongroup.name)
- self.assertEqual(question_group[1].questiongroup.name, question_group2.questiongroup.name)
+ question_group1 = QuestionGroup.objects.get(pk=1)
+ question_group2 = QuestionGroup.objects.get(pk=2)
+ self.assertEqual(question_group[0], question_group1)
+ self.assertEqual(question_group[1], question_group2)
def test_get_group_for_index_invalid_index(self):
'''
@@ -348,6 +389,14 @@ def test_required_fields(self):
class AnswerSetTestCase(TestCase):
fixtures = ['test_questionnaire_fixtures_formodels.json']
+ def setUp(self):
+
+ self.test_questionnaire = Questionnaire.objects.get(id=1)
+ self.test_group = QuestionGroup.objects.get(id=1)
+ self.test_user = User.objects.create_user('test', 'test@test.com', 'password')
+ self.test_answer_set = AnswerSet.objects.create(user=self.test_user,
+ questionnaire = self.test_questionnaire,
+ questiongroup = self.test_group)
def test_fields(self):
'''
An AnswerSet should have the following required fields:
@@ -373,7 +422,7 @@ def test_get_latest_question_answers(self):
'''
#generate an AnswerSet
- test_answer_set = AnswerSet(user=User.objects.create_user('test', 'test@test.com', 'test'),
+ test_answer_set = AnswerSet(user=User.objects.create_user('test_user', 'test@test.com', 'test'),
questionnaire=Questionnaire.objects.get(pk=1),
questiongroup=QuestionGroup.objects.get(pk=1))
#patch the LatestQuestionAnswer objects function
@@ -432,6 +481,42 @@ def test_get_latest_question_answer_in_order(self):
self.assertEqual(test_answer_set.get_latest_question_answer_in_order(),[qa4,qa2,qa1])
mocked_function.assert_called_once_with()
+
+ def fabricate_question_answer(self, question_id, answer_set, response):
+ '''
+ Fabricates QuestionAnswers and put them into the database
+ '''
+ question = Question.objects.get(id = question_id)
+ return QuestionAnswer.objects.create(question=question,
+ answer_set=answer_set,
+ answer = response)
+
+ def test_is_complete_no_answers(self):
+ '''
+ If there are no answers associated with this AnswerSet then this function should return False
+ '''
+ self.assertFalse(self.test_answer_set.is_complete())#we haven't added any questionAnswers yet!
+
+ def test_is_complete_incomplete_answers(self):
+ '''
+ If there are some answers, but not at least one answer for each question in the group then this
+ function should return False
+ '''
+ #add a questionAnswers for the first 2 but the not the last question
+ for index in range(1,3):
+ self.fabricate_question_answer(index, self.test_answer_set, 'response')
+
+ self.assertFalse(self.test_answer_set.is_complete())
+
+ def test_is_complete_all_answers(self):
+ '''
+ If there is at least one (remember we can have more than one answer for a question) questionAnswer for each
+ question then this should return True
+ '''
+ for index in range(1,4):
+ self.fabricate_question_answer(index, self.test_answer_set, 'response')
+ self.assertTrue(self.test_answer_set.is_complete())
+
class QuestionAnswerTestCase(TestCase):
fixtures = ['test_questionnaire_fixtures_formodels.json']
def test_fields(self):
@@ -275,7 +275,85 @@ def test_post_success_name_provided_and_in_post_lastgroup(self):
self.assertEqual(len(test_answer_set.questionanswer_set.all()), 3)
self.assertRedirects(resp, reverse('questionnaire_finish'))
+ def test_valid_post_with_limit_of_one(self):
+ '''
+ A group limit of 1 means that you only want to do one group: the group that you are requesting in the
+ order_index.
+ If a group limit of one is passed in, and the questionnaire contains more than one
+ questiongroup after the group being requested, then a valid post will cause a redirect to the
+ finish_url, and not to the next questiongroup as would be the case for a limit of 0 or more than 1
+ '''
+ test_questionnaire = Questionnaire.objects.create(name='test_questionnaire')
+ test_group = QuestionGroup.objects.get(pk=1) #we know that this is in the db from the fixture
+
+ for index in range(3): #add the same group thrice
+ test_questionnaire.add_question_group(test_group)
+
+ self.client.login(username='user', password='password')
+
+ url = reverse('handle_next_questiongroup_form', kwargs={'questionnaire_id': test_questionnaire.id, 'order_index':0, 'group_limit':1})
+ post_data = {u'1': [u'b'], u'2': [u'b'], u'3': 1} #a valid post for this questiongroup
+ resp = self.client.post(url,post_data)
+
+ self.assertEqual(resp.status_code, 302)
+ self.assertEqual(resp['Location'], 'http://testserver/questionnaire/finish/')#this is what is configured as the finish url in the url conf
+
+
+ def test_valid_post_with_limit_of_greater_than_one_groups_avaialble(self):
+ '''
+ A group limit of more than one means that you want to do the current group (defined by order_index)
+ and then (group_index -1) more groups (if there are that many in the sequence).
+
+ In this case there are groups available to fulfill this request. for example there are three
+ groups, and we are requesting the first group with a group_limit of 2, in this case
+ a valid post to the url will decrement the current group_limit when it redirects to the url
+ for the next group (Eventually it will hit 1 and then that will be the last group carried out)
+ '''
+
+ test_questionnaire = Questionnaire.objects.create(name='test_questionnaire')
+ test_group = QuestionGroup.objects.get(pk=1) #we know that this is in the db from the fixture
+
+ for index in range(3): #add the same group thrice
+ test_questionnaire.add_question_group(test_group)
+
+ self.client.login(username='user', password='password')
+ url = reverse('handle_next_questiongroup_form', kwargs={'questionnaire_id': test_questionnaire.id, 'order_index':0, 'group_limit':2})
+ post_data = {u'1': [u'b'], u'2': [u'b'], u'3': 1} #a valid post for this questiongroup
+ resp = self.client.post(url,post_data)
+
+ self.assertEqual(resp.status_code, 302)
+ redirect_url = reverse('handle_next_questiongroup_form', kwargs={'questionnaire_id': test_questionnaire.id, 'order_index':1,#order incremented by 1
+ 'group_limit':1}) #limit reduced by 1
+
+ self.assertEqual(resp['Location'], 'http://testserver' + redirect_url)#this is what is configured as the finish url in the url conf
+
+
+ def test_valid_post_with_limit_of_greater_than_one_groups_not_avaialble(self):
+ '''
+ A group limit of more than one means that you want to do the current group (defined by order_index)
+ and then (group_index -1) more groups (if there are that many in the sequence).
+
+ In this case there are not enough groups to fulfill this request for example there
+ are 3 groups and we are requesting group 2 with a group limit of 3. IN this case the questionnaire
+ will simply terminate as usual after the last group.
+ '''
+
+ test_questionnaire = Questionnaire.objects.create(name='test_questionnaire')
+ test_group = QuestionGroup.objects.get(pk=1) #we know that this is in the db from the fixture
+
+ #only add the group once this time, so there is only one group in the list
+ test_questionnaire.add_question_group(test_group)
+
+ self.client.login(username='user', password='password')
+
+ url = reverse('handle_next_questiongroup_form', kwargs={'questionnaire_id': test_questionnaire.id, 'order_index':0, 'group_limit':2})
+ post_data = {u'1': [u'b'], u'2': [u'b'], u'3': 1} #a valid post for this questiongroup
+ resp = self.client.post(url,post_data)
+
+ self.assertEqual(resp.status_code, 302)
+ self.assertEqual(resp['Location'], 'http://testserver/questionnaire/finish/')#this is what is configured as the finish url in the url conf
+
View
@@ -12,7 +12,13 @@
name='questionnaire_index',
kwargs={'template_name':'questionnaire/questionnaire_index.html'}),
-
+ url(r'^qs/(?P<questionnaire_id>\d+)/(?P<order_index>\d+)/(?P<group_limit>\d+)/$',
+ view = 'do_questionnaire',
+ name = 'handle_next_questiongroup_form',
+ kwargs={'template_name':'questionnaire/questionform.html',
+ 'next_form_name':'handle_next_questiongroup_form',
+ 'finished_url':'/questionnaire/finish/'},),
+
url(r'^qs/(?P<questionnaire_id>\d+)/(?P<order_index>\d+)/$',
view = 'do_questionnaire',
name = 'handle_next_questiongroup_form',
Oops, something went wrong.

0 comments on commit 410e0c3

Please sign in to comment.