From 30c081547ed2bf1bf5607b83a4c97dac60bddb71 Mon Sep 17 00:00:00 2001 From: ariannedee Date: Fri, 21 Aug 2020 12:01:32 -0700 Subject: [PATCH] Reimplement all logic for getting data and checking step completeness --- edivorce/apps/core/tests.py | 530 +++++------------- edivorce/apps/core/utils/conditional_logic.py | 69 +++ edivorce/apps/core/utils/derived.py | 62 +- .../apps/core/utils/question_step_mapping.py | 4 + edivorce/apps/core/utils/step_completeness.py | 119 +--- edivorce/apps/core/utils/user_response.py | 208 +++---- edivorce/apps/core/views/main.py | 55 +- edivorce/apps/core/views/pdf.py | 4 +- edivorce/fixtures/Question.json | 69 +-- 9 files changed, 429 insertions(+), 691 deletions(-) create mode 100644 edivorce/apps/core/utils/conditional_logic.py diff --git a/edivorce/apps/core/tests.py b/edivorce/apps/core/tests.py index a6d52905..f7503626 100644 --- a/edivorce/apps/core/tests.py +++ b/edivorce/apps/core/tests.py @@ -3,592 +3,346 @@ from edivorce.apps.core.models import UserResponse, Question, BceidUser from edivorce.apps.core.utils.step_completeness import is_complete from edivorce.apps.core.utils.question_step_mapping import question_step_mapping - # Create your tests here. +from edivorce.apps.core.utils.user_response import get_data_for_user, get_step_responses + + class UserResponseTestCase(TestCase): fixtures = ['Question.json'] def setUp(self): - BceidUser.objects.create(user_guid='1234') + self.user = BceidUser.objects.create(user_guid='1234') + + def check_completeness(self, step): + responses_dict = get_data_for_user(self.user) + responses_dict_by_step = get_step_responses(responses_dict) + return is_complete(responses_dict_by_step[step])[0] + + def create_response(self, question, value): + UserResponse.objects.create(bceid_user=self.user, question=Question.objects.get(key=question), value=value) def test_which_order(self): step = 'which_orders' - questions = question_step_mapping[step] - user = BceidUser.objects.get(user_guid='1234') # No response should be False - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) # All required question - create_response(user, 'want_which_orders', '["nothing"]') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], True) + self.create_response('want_which_orders', '["nothing"]') + self.assertEqual(self.check_completeness(step), True) # Put empty response UserResponse.objects.filter(question_id='want_which_orders').update(value="[]") - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) def test_your_info(self): step = 'your_information' - questions = question_step_mapping[step] - user = BceidUser.objects.get(user_guid='1234') # No response should be False - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) # Testing required questions # Missing few required questions - create_response(user, 'name_you', 'John Doe') - create_response(user, 'last_name_before_married_you', 'Jackson') - create_response(user, 'birthday_you', '11/11/1111') - create_response(user, 'occupation_you', 'Plumber') + self.create_response('name_you', 'John Doe') + self.create_response('last_name_before_married_you', 'Jackson') + self.create_response('birthday_you', '11/11/1111') + self.create_response('occupation_you', 'Plumber') - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) # Few required questions with one checking question with hidden question not shown - create_response(user, 'lived_in_bc_you', '11/11/1111') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.create_response('lived_in_bc_you', '11/11/1111') + self.assertEqual(self.check_completeness(step), False) # All required questions with one checking question with hidden question not shown - create_response(user, 'last_name_born_you', 'Jackson') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.create_response('last_name_born_you', 'Jackson') + self.assertEqual(self.check_completeness(step), False) - # All required questions with one checking question with hidden question missing UserResponse.objects.filter(question_id='lived_in_bc_you').update(value="Moved to B.C. on") - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) # All required questions with one checking question with hidden question - create_response(user, 'moved_to_bc_date_you', '12/12/1212') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.create_response('moved_to_bc_date_you', '12/12/1212') + self.assertEqual(self.check_completeness(step), False) # All required questions with two checking question with one hidden and one shown - create_response(user, 'any_other_name_you', 'NO') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], True) + self.create_response('any_other_name_you', 'NO') + self.assertEqual(self.check_completeness(step), True) # All required questions with two checking question with one hidden question missing UserResponse.objects.filter(question_id='any_other_name_you').update(value="YES") - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) # All required questions with all checking question with all hidden questions - create_response(user, 'other_name_you', '[["also known as","Smith"]]') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], True) + self.create_response('other_name_you', '[["also known as","Smith"]]') + self.assertEqual(self.check_completeness(step), True) # Put empty response UserResponse.objects.filter(question_id='other_name_you').update(value='[["",""]]') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) def test_your_spouse(self): step = 'your_spouse' - questions = question_step_mapping[step] - user = BceidUser.objects.get(user_guid='1234') # No response should be False - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) # Testing required questions # Missing few required questions - create_response(user, 'name_spouse', 'John Doe') - create_response(user, 'last_name_before_married_spouse', 'Jackson') - create_response(user, 'birthday_spouse', '11/11/1111') - create_response(user, 'occupation_spouse', 'Electrician') + self.create_response('name_spouse', 'John Doe') + self.create_response('last_name_before_married_spouse', 'Jackson') + self.create_response('birthday_spouse', '11/11/1111') + self.create_response('occupation_spouse', 'Electrician') - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) # Few required questions with one checking question with hidden question not shown - create_response(user, 'any_other_name_spouse', 'NO') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.create_response('any_other_name_spouse', 'NO') + self.assertEqual(self.check_completeness(step), False) # All required questions with one checking question with hidden question not shown - create_response(user, 'last_name_born_spouse', 'Jackson') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.create_response('last_name_born_spouse', 'Jackson') + self.assertEqual(self.check_completeness(step), False) # All required questions with one checking question with hidden question missing UserResponse.objects.filter(question_id='any_other_name_spouse').update(value="YES") - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) # All required questions with one checking question with hidden question - create_response(user, 'lived_in_bc_spouse', 'Since birth') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.create_response('lived_in_bc_spouse', 'Since birth') + self.assertEqual(self.check_completeness(step), False) # All required questions with two checking question with one hidden and one shown - create_response(user, 'other_name_spouse', '[["also known as","Smith"]]') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], True) + self.create_response('other_name_spouse', '[["also known as","Smith"]]') + self.assertEqual(self.check_completeness(step), True) # All required questions with two checking question with one hidden question missing UserResponse.objects.filter(question_id='lived_in_bc_spouse').update(value="Moved to B.C. on") - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) # All required questions with all checking question with all hidden questions - create_response(user, 'moved_to_bc_date_spouse', '12/12/1212') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], True) + self.create_response('moved_to_bc_date_spouse', '12/12/1212') + self.assertEqual(self.check_completeness(step), True) # Put empty response UserResponse.objects.filter(question_id='name_spouse').update(value="") - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) # Put empty response UserResponse.objects.filter(question_id='other_name_spouse').update(value='[["",""]]') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) def test_your_marriage(self): step = 'your_marriage' - questions = question_step_mapping[step] - user = BceidUser.objects.get(user_guid='1234') # No response should be False - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) - - # Some required questions - create_response(user, 'when_were_you_live_married_like', '12/12/2007') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) # Some required questions - create_response(user, 'when_were_you_married', '12/12/2008') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.create_response('when_were_you_live_married_like', '12/12/2007') + self.assertEqual(self.check_completeness(step), False) - # Some required questions - create_response(user, 'marital_status_before_you', 'Never married') + self.create_response('when_were_you_married', '12/12/2008') + self.assertEqual(self.check_completeness(step), False) - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) - - # Some required questions - create_response(user, 'marital_status_before_spouse', 'Widowed') + self.create_response('marital_status_before_you', 'Never married') + self.assertEqual(self.check_completeness(step), False) - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.create_response('marital_status_before_spouse', 'Widowed') + self.assertEqual(self.check_completeness(step), False) - # Some required questions - create_response(user, 'where_were_you_married_city', 'Vancouver') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) - - # Some required questions - create_response(user, 'where_were_you_married_prov', 'BC') + self.create_response('where_were_you_married_city', 'Vancouver') + self.assertEqual(self.check_completeness(step), False) - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.create_response('where_were_you_married_prov', 'BC') + self.assertEqual(self.check_completeness(step), False) # All required questions - create_response(user, 'where_were_you_married_country', 'Canada') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], True) + self.create_response('where_were_you_married_country', 'Canada') + self.assertEqual(self.check_completeness(step), True) # All required questions but missing conditional question UserResponse.objects.filter(question_id='where_were_you_married_country').update(value="Other") - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) # All required questions - create_response(user, 'where_were_you_married_other_country', 'Peru') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], True) + self.create_response('where_were_you_married_other_country', 'Peru') + self.assertEqual(self.check_completeness(step), True) def test_your_separation(self): step = 'your_separation' - questions = question_step_mapping[step] - user = BceidUser.objects.get(user_guid='1234') - # No response should be False - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) # All required question - create_response(user, 'no_reconciliation_possible', 'I agree') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.create_response('no_reconciliation_possible', 'I agree') + self.assertEqual(self.check_completeness(step), False) # Put empty response UserResponse.objects.filter(question_id='no_reconciliation_possible').update(value="") - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) def test_spousal_support(self): step = 'spousal_support' - questions = question_step_mapping[step] - user = BceidUser.objects.get(user_guid='1234') # No response should be False - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) # One required question - create_response(user, 'spouse_support_details', 'I will support you') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.create_response('spouse_support_details', 'I will support you') + self.assertEqual(self.check_completeness(step), False) # Two required questions - create_response(user, 'spouse_support_act', 'Family Law') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], True) + self.create_response('spouse_support_act', 'Family Law') + self.assertEqual(self.check_completeness(step), True) # Remove first added required response to test the second required question UserResponse.objects.get(question_id='spouse_support_details').delete() - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) # Put empty response UserResponse.objects.filter(question_id='spouse_support_details').update(value="") - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) def test_property_and_debt(self): step = 'property_and_debt' - questions = question_step_mapping[step] - user = BceidUser.objects.get(user_guid='1234') # No response should be False - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) # All required question with no hidden shown - create_response(user, 'deal_with_property_debt', 'Equal division') + self.create_response('deal_with_property_debt', 'Equal division') - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], True) + self.assertEqual(self.check_completeness(step), True) # All required question with hidden shown but no response UserResponse.objects.filter(question_id='deal_with_property_debt').update(value="Unequal division") - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) # Only one required question with hidden shown and answered - create_response(user, 'how_to_divide_property_debt', 'Do not divide them') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], True) + self.create_response('how_to_divide_property_debt', 'Do not divide them') - # Only two required question with hidden shown and answered - # NOTE: want_other_property_claims not in use anymore - # create_response(user, 'want_other_property_claims', '["Ask for other property claims"]') - # - # lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - # self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), True) - # All required question with hidden shown and answered - create_response(user, 'other_property_claims', 'Want these property claims') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], True) + # All required question with optional fields + self.create_response('other_property_claims', 'Want these property claims') + self.assertEqual(self.check_completeness(step), True) # Put empty response - # UserResponse.objects.filter(question_id='want_other_property_claims').update(value="") - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], True) + UserResponse.objects.filter(question_id='want_other_property_claims').update(value="") + self.assertEqual(self.check_completeness(step), True) def test_other_orders(self): step = 'other_orders' - questions = question_step_mapping[step] - user = BceidUser.objects.get(user_guid='1234') # No response should be False - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) # All required question + self.create_response('name_change_you', 'NO') + self.assertEqual(self.check_completeness(step), False) - create_response(user, 'name_change_you', 'NO') - self.assertEqual(is_complete(step, lst)[0], False) - - create_response(user, 'name_change_spouse', 'NO') - create_response(user, 'other_orders_detail', 'I want more orders') + self.create_response('name_change_spouse', 'NO') + self.create_response('other_orders_detail', 'I want more orders') - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], True) + self.assertEqual(self.check_completeness(step), True) # make incomplete UserResponse.objects.filter(question_id='name_change_spouse').update(value="YES") + self.assertEqual(self.check_completeness(step), False) - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) - - create_response(user, 'name_change_spouse_fullname', 'new name') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], True) + self.create_response('name_change_spouse_fullname', 'new name') + self.assertEqual(self.check_completeness(step), True) # Put empty response UserResponse.objects.filter(question_id='other_orders_detail').update(value="") - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], True) + self.assertEqual(self.check_completeness(step), True) def test_other_questions(self): step = 'other_questions' - questions = question_step_mapping[step] - user = BceidUser.objects.get(user_guid='1234') # No response should be False - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) - - # One required question - create_response(user, 'address_to_send_official_document_street_you', '123 Cambie st') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) - # Two required question - create_response(user, 'address_to_send_official_document_city_you', 'Vancouver') + # Some required question + self.create_response('address_to_send_official_document_street_you', '123 Cambie st') + self.assertEqual(self.check_completeness(step), False) - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.create_response('address_to_send_official_document_city_you', 'Vancouver') + self.assertEqual(self.check_completeness(step), False) - # Three required question - create_response(user, 'address_to_send_official_document_prov_you', 'BC') + self.create_response('address_to_send_official_document_prov_you', 'BC') + self.assertEqual(self.check_completeness(step), False) - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) - - # Four required question - create_response(user, 'address_to_send_official_document_country_you', 'Canada') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.create_response('address_to_send_official_document_country_you', 'Canada') + self.assertEqual(self.check_completeness(step), False) # All required questions for you - create_response(user, 'address_to_send_official_document_postal_code_you', 'Canada') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.create_response('address_to_send_official_document_postal_code_you', 'Canada') + self.assertEqual(self.check_completeness(step), False) # One required question for spouse - create_response(user, 'address_to_send_official_document_street_spouse', '123 Cambie st') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.create_response('address_to_send_official_document_street_spouse', '123 Cambie st') + self.assertEqual(self.check_completeness(step), False) # Two required question for spouse - create_response(user, 'address_to_send_official_document_city_spouse', 'Vancouver') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.create_response('address_to_send_official_document_city_spouse', 'Vancouver') + self.assertEqual(self.check_completeness(step), False) # Three required question for spouse - create_response(user, 'address_to_send_official_document_prov_spouse', 'BC') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.create_response('address_to_send_official_document_prov_spouse', 'BC') + self.assertEqual(self.check_completeness(step), False) # Four required question for spouse - create_response(user, 'address_to_send_official_document_country_spouse', 'Canada') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.create_response('address_to_send_official_document_country_spouse', 'Canada') + self.assertEqual(self.check_completeness(step), False) # All required questions - create_response(user, 'divorce_take_effect_on', 'the 31st day after the date of this order') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], True) + self.create_response('divorce_take_effect_on', 'the 31st day after the date of this order') + self.assertEqual(self.check_completeness(step), True) # Missing conditional required question UserResponse.objects.filter(question_id='divorce_take_effect_on').update(value="specific date") - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) # All required questions - create_response(user, 'divorce_take_effect_on_specific_date', '12/12/2018') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], True) + self.create_response('divorce_take_effect_on_specific_date', '12/12/2018') + self.assertEqual(self.check_completeness(step), True) # All required questions for spouse and you - create_response(user, 'address_to_send_official_document_postal_code_spouse', 'Canada') + self.create_response('address_to_send_official_document_postal_code_spouse', 'Canada') - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], True) + self.assertEqual(self.check_completeness(step), True) # All required questions for spouse and you with empty email(optional so still true) - create_response(user, 'address_to_send_official_document_email_you', 'a@example.com') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], True) + self.create_response('address_to_send_official_document_email_you', 'a@example.com') + self.assertEqual(self.check_completeness(step), True) # Testing other country missing UserResponse.objects.filter(question_id='address_to_send_official_document_country_spouse').update(value="Other") - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) # All required questions - create_response(user, 'address_to_send_official_document_other_country_spouse', 'Mexico') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], True) + self.create_response('address_to_send_official_document_other_country_spouse', 'Mexico') + self.assertEqual(self.check_completeness(step), True) # Set Specific date on to empty UserResponse.objects.filter(question_id='divorce_take_effect_on_specific_date').update(value="") - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', - 'question__conditional_target', - 'question__reveal_response', - 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) def test_filing_locations(self): step = 'filing_locations' - questions = question_step_mapping[step] - user = BceidUser.objects.get(user_guid='1234') # No response should be False - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) + self.assertEqual(self.check_completeness(step), False) # All required question - create_response(user, 'court_registry_for_filing', 'Vancouver') - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], True) + self.create_response('court_registry_for_filing', 'Vancouver') + self.assertEqual(self.check_completeness(step), True) # Put empty response UserResponse.objects.filter(question_id='court_registry_for_filing').update(value="") - - lst = UserResponse.objects.filter(question_id__in=questions).values('question_id', 'value', 'question__conditional_target', 'question__reveal_response', 'question__required') - self.assertEqual(is_complete(step, lst)[0], False) - - -# Helper functions -def create_response(user, question, value): - UserResponse.objects.create(bceid_user=user, question=Question.objects.get(key=question), value=value) + self.assertEqual(self.check_completeness(step), False) diff --git a/edivorce/apps/core/utils/conditional_logic.py b/edivorce/apps/core/utils/conditional_logic.py new file mode 100644 index 00000000..cc55daa9 --- /dev/null +++ b/edivorce/apps/core/utils/conditional_logic.py @@ -0,0 +1,69 @@ +import json + + +def get_children(questions_dict): + children_json = questions_dict.get('claimant_children', '[]') + if isinstance(children_json, dict): + children_json = children_json.get('value', '[]') + return json.loads(children_json) + + +def determine_sole_custody(questions_dict): + child_list = get_children(questions_dict) + return (all([child['child_live_with'] == 'Lives with you' for child in child_list]) or + all([child['child_live_with'] == 'Lives with spouse' for child in child_list])) + + +def determine_shared_custody(questions_dict): + child_list = get_children(questions_dict) + return any([child['child_live_with'] == 'Lives with both' + for child in child_list]) + + +def determine_split_custody(questions_dict): + child_list = get_children(questions_dict) + with_you = 0 + with_spouse = 0 + with_both = 0 + for child in child_list: + if child['child_live_with'] == 'Lives with you': + with_you += 1 + elif child['child_live_with'] == 'Lives with spouse': + with_spouse += 1 + elif child['child_live_with'] == 'Lives with both': + with_both += 1 + return (with_you > 0 and (with_spouse + with_both > 0) or + with_spouse > 0 and (with_you + with_both > 0)) + + +def determine_child_over_19_supported(questions_dict): + try: + children_over_19 = float(questions_dict.get('number_children_over_19', 0)) + except ValueError: + children_over_19 = 0 + + support = json.loads(questions_dict.get('children_financial_support', '[]')) + has_children_of_marriage = questions_dict.get('children_of_marriage', '') == 'YES' + return (len(support) > 0 and children_over_19 > 0 and + 'NO' not in support and has_children_of_marriage) + + +def determine_missing_undue_hardship_reasons(questions_dict): + claiming_undue_hardship = questions_dict.get('claiming_undue_hardship', '') == 'YES' + if claiming_undue_hardship: + at_least_one_of = ["claimant_debts", "claimant_expenses", "supporting_non_dependents", "supporting_dependents", + "supporting_disabled", "undue_hardship"] + for question in at_least_one_of: + value = questions_dict.get(question) + if value: + try: + items = json.loads(value) + for item in items: + for key in item: + if item[key]: + return False + except json.JSONDecodeError: + if value: + return False + + return True diff --git a/edivorce/apps/core/utils/derived.py b/edivorce/apps/core/utils/derived.py index 8cc601f9..6b0e57c9 100644 --- a/edivorce/apps/core/utils/derived.py +++ b/edivorce/apps/core/utils/derived.py @@ -13,6 +13,8 @@ under the _derived_ key. import json +from edivorce.apps.core.utils import conditional_logic + # This array is order sensitive: later functions may depend on values from # earlier ones DERIVED_DATA = [ @@ -72,6 +74,7 @@ DERIVED_DATA = [ 'pursuant_parenting_arrangement', 'pursuant_child_support', 'sole_custody', + 'missing_undue_hardship_details', ] @@ -150,9 +153,7 @@ def show_fact_sheet_b(responses, derived): If any child lives with both parents, custody is shared, so Fact Sheet B is indicated. """ - - return any([child['child_live_with'] == 'Lives with both' - for child in derived['children']]) + return conditional_logic.determine_shared_custody(responses) def show_fact_sheet_c(responses, derived): @@ -160,19 +161,7 @@ def show_fact_sheet_c(responses, derived): If any child lives with one parent and there's another child who lives with the other parent or is shared, Fact Sheet C is indicated. """ - - with_you = 0 - with_spouse = 0 - with_both = 0 - for child in derived['children']: - if child['child_live_with'] == 'Lives with you': - with_you += 1 - elif child['child_live_with'] == 'Lives with spouse': - with_spouse += 1 - elif child['child_live_with'] == 'Lives with both': - with_both += 1 - return (with_you > 0 and (with_spouse + with_both > 0) or - with_spouse > 0 and (with_you + with_both > 0)) + return conditional_logic.determine_split_custody(responses) def show_fact_sheet_d(responses, derived): @@ -180,15 +169,7 @@ def show_fact_sheet_d(responses, derived): If a claimaint is claiming financial support for a child of the marriage over 19, Fact Sheet D is indicated. """ - - try: - children_over_19 = float(responses.get('number_children_over_19', 0)) - except ValueError: - children_over_19 = 0 - - support = json.loads(responses.get('children_financial_support', '[]')) - return (len(support) > 0 and children_over_19 > 0 and - 'NO' not in support and has_children_of_marriage(responses, derived)) + return conditional_logic.determine_child_over_19_supported(responses) def show_fact_sheet_e(responses, derived): @@ -242,11 +223,11 @@ def show_fact_sheet_f_spouse(responses, derived): def has_fact_sheets(responses, derived): """ Return whether or not the user is submitting fact sheets """ - return any([derived['show_fact_sheet_b'], derived['show_fact_sheet_c'], derived['show_fact_sheet_d'], derived['show_fact_sheet_e'], derived['show_fact_sheet_f'], ]) + def child_support_payor_b(responses, derived): """ Return who the payor is depends on the monthly amount from Factsheet B """ try: @@ -264,10 +245,11 @@ def child_support_payor_b(responses, derived): elif amount_1 < amount_2: payor = 'spouse' else: - payor = 'both' + payor = 'both' return payor + def child_support_payor_c(responses, derived): """ Return who the payor is depends on the monthly amount from Factsheet C """ try: @@ -285,10 +267,11 @@ def child_support_payor_c(responses, derived): elif amount_1 < amount_2: payor = 'spouse' else: - payor = 'both' + payor = 'both' return payor + def guideline_amounts_difference_b(responses, derived): """ Return the difference between the guideline amounts to be paid by @@ -307,6 +290,7 @@ def guideline_amounts_difference_b(responses, derived): return abs(amount_1 - amount_2) + def guideline_amounts_difference_c(responses, derived): """ Return the difference between the guideline amounts to be paid by @@ -325,6 +309,7 @@ def guideline_amounts_difference_c(responses, derived): return abs(amount_1 - amount_2) + def guideline_amounts_difference_total(responses, derived): """ Return the sum of the guideline amounts B and C @@ -339,14 +324,15 @@ def guideline_amounts_difference_total(responses, derived): if payor_b == payor_c: return amount_b + amount_c else: - return abs(amount_b - amount_c) + return abs(amount_b - amount_c) + def schedule_1_amount(responses, derived): """ Return the amount as defined in schedule 1 for child support """ try: if derived['show_fact_sheet_b'] or derived['show_fact_sheet_c']: - return derived['guideline_amounts_difference_total'] + return derived['guideline_amounts_difference_total'] else: return float(responses.get('payor_monthly_child_support_amount', 0)) except ValueError: @@ -540,13 +526,11 @@ def total_monthly_support_1_and_a(responses, derived): total += derived['total_section_seven_expenses'] return total + def total_child_support_payment_a(response, derived): """ Return the total monthly child support payable by the payor for Fact Sheet A """ total = 0 - sole_custody = (all([child['child_live_with'] == 'Lives with you' for child in derived['children']]) or - all([child['child_live_with'] == 'Lives with spouse' for child in derived['children']])) - - if sole_custody: + if sole_custody(response, derived): total += derived['schedule_1_amount'] else: if derived['show_fact_sheet_b']: @@ -717,8 +701,8 @@ def sole_custody(responses, derived): """ Return True if either parent has sole custody of the children """ - you_have_sole_custody = all([child['child_live_with'] == 'Lives with you' - for child in derived['children']]) - spouse_has_sole_custody = all([child['child_live_with'] == 'Lives with spouse' - for child in derived['children']]) - return you_have_sole_custody or spouse_has_sole_custody + return conditional_logic.determine_sole_custody(responses) + + +def missing_undue_hardship_details(responses, derived): + return conditional_logic.determine_missing_undue_hardship_reasons(responses) diff --git a/edivorce/apps/core/utils/question_step_mapping.py b/edivorce/apps/core/utils/question_step_mapping.py index 53a09e21..d34bf108 100644 --- a/edivorce/apps/core/utils/question_step_mapping.py +++ b/edivorce/apps/core/utils/question_step_mapping.py @@ -128,6 +128,9 @@ question_step_mapping = { 'your_spouse_child_support_paid_b', 'your_child_support_paid_c', 'your_spouse_child_support_paid_c', + 'agree_to_guideline_child_support_amount', + 'appropriate_spouse_paid_child_support', + 'suggested_child_support', 'extra_ordinary_expenses_you', 'extra_ordinary_expenses_spouse', 'additional_relevant_spouse_children_info', @@ -184,6 +187,7 @@ page_step_mapping = { 'property': 'property_and_debt', 'other_orders': 'other_orders', 'other_questions': 'other_questions', + 'filing_locations': 'filing_locations', } diff --git a/edivorce/apps/core/utils/step_completeness.py b/edivorce/apps/core/utils/step_completeness.py index af388074..1142c6ab 100644 --- a/edivorce/apps/core/utils/step_completeness.py +++ b/edivorce/apps/core/utils/step_completeness.py @@ -1,8 +1,7 @@ -import ast from django.urls import reverse from edivorce.apps.core.models import Question -from edivorce.apps.core.utils.question_step_mapping import question_step_mapping, pre_qual_step_question_mapping +from edivorce.apps.core.utils.question_step_mapping import pre_qual_step_question_mapping def evaluate_numeric_condition(target, reveal_response): @@ -32,14 +31,14 @@ def evaluate_numeric_condition(target, reveal_response): return None -def get_step_status(responses_by_step): +def get_step_completeness(responses_by_step): status_dict = {} missing_response_dict = {} - for step, lst in responses_by_step.items(): - if not lst: + for step, responses_list in responses_by_step.items(): + if len(responses_list) == 0: status_dict[step] = "Not started" else: - complete, missing_responses = is_complete(step, lst) + complete, missing_responses = is_complete(responses_list) if complete: status_dict[step] = "Complete" else: @@ -48,69 +47,12 @@ def get_step_status(responses_by_step): return status_dict, missing_response_dict -def is_complete(step, lst): - """ - Check required field of question for complete state - Required: question is always require user response to be complete - Conditional: Optional question needed depends on reveal_response value of conditional_target. - """ - if not lst: - return False, [] - question_list = Question.objects.filter(key__in=question_step_mapping[step]) - required_list = list(question_list.filter(required='Required').values_list("key", flat=True)) - conditional_list = list(question_list.filter(required='Conditional')) - - complete = True +def is_complete(response_list): missing_responses = [] - - for question_key in required_list: - # everything in the required_list is required - if not __has_value(question_key, lst): - complete = False - missing_responses += [question_key] - elif question_key == "special_extraordinary_expenses": - has_extraordinary_expenses = __get_value("special_extraordinary_expenses", lst) - if has_extraordinary_expenses.lower() == 'yes': - # validate at least one item is > 0 - special_expenses_keys = ["child_care_expenses", "annual_child_care_expenses", "children_healthcare_premiums", - "annual_children_healthcare_premiums", "health_related_expenses", "annual_health_related_expenses", - "extraordinary_educational_expenses", "annual_extraordinary_educational_expenses", - "post_secondary_expenses", "annual_post_secondary_expenses", "extraordinary_extracurricular_expenses", - "annual_extraordinary_extracurricular_expenses"] - for expense in special_expenses_keys: - value = __get_value(expense, lst) - if value and float(value) > 0: - break - else: - missing_responses.append('special_extraordinary_expenses_details') - - for question in conditional_list: - # check condition for payor_monthly_child_support_amount - # which needs sole_custody to be computed separately - # payor_monthly_child_support_ammount is required only if sole_custody is True - if question.key == "payor_monthly_child_support_amount": - for target in lst: - if target["question_id"] == "claimant_children": - child_list = ast.literal_eval(target['value']) - sole_custody = (all([child['child_live_with'] == 'Lives with you' for child in child_list]) or - all([child['child_live_with'] == 'Lives with spouse' for child in child_list])) - if sole_custody: - if not __has_value(question.key, lst): - complete = False - missing_responses += [question.key] - break - else: - # find the response to the conditional target - for target in lst: - if target["question_id"] == question.conditional_target: - if __condition_met(question.reveal_response, target, lst): - # the condition was met then the question is required. - # ... so check if it has a value - if not __has_value(question.key, lst): - complete = False - missing_responses += [question.key] - - return complete, missing_responses + for question_dict in response_list: + if question_dict['error']: + missing_responses.append(question_dict) + return len(missing_responses) == 0, missing_responses def get_formatted_incomplete_list(missed_question_keys): @@ -131,44 +73,3 @@ def get_formatted_incomplete_list(missed_question_keys): 'step_url': reverse('prequalification', kwargs={'step': step}) }) return missed_questions - - -def __condition_met(reveal_response, target, lst): - # check whether using a numeric condition - numeric_condition_met = evaluate_numeric_condition(target["value"], reveal_response) - if numeric_condition_met is None: - # handle special negation options. ex) '!NO' matches anything but 'NO' - if reveal_response.startswith('!'): - if target["value"] == "" or target["value"] == reveal_response[1:]: - return False - elif target["value"] != reveal_response: - return False - elif numeric_condition_met is False: - return False - - # return true if the target is not Conditional - if target['question__required'] != 'Conditional': - return True - else: - # if the target is Conditional and the condition was met, check the target next - reveal_response = target["question__reveal_response"] - conditional_target = target["question__conditional_target"] - for new_target in lst: - if new_target["question_id"] == conditional_target: - # recursively search up the tree - return __condition_met(reveal_response, new_target, lst) - - # if the for loop above didn't find the target, then the target question - # is unanswered and the condition was not met - return False - - -def __get_value(key, lst): - for user_response in lst: - if user_response["question_id"] == key: - return user_response["value"] - - -def __has_value(key, lst): - value = __get_value(key, lst) - return value and value != "" and value != "[]" and value != '[["",""]]' and value != "\n" diff --git a/edivorce/apps/core/utils/user_response.py b/edivorce/apps/core/utils/user_response.py index c1d9933f..ee1749a2 100644 --- a/edivorce/apps/core/utils/user_response.py +++ b/edivorce/apps/core/utils/user_response.py @@ -1,90 +1,128 @@ from edivorce.apps.core.models import UserResponse, Question -from edivorce.apps.core.utils.question_step_mapping import question_step_mapping +from edivorce.apps.core.utils import conditional_logic +from edivorce.apps.core.utils.question_step_mapping import page_step_mapping, question_step_mapping from edivorce.apps.core.utils.step_completeness import evaluate_numeric_condition from collections import OrderedDict -def get_responses_from_db(bceid_user): - """ Get UserResponses from the database for a user.""" - married, married_questions, responses = __get_data(bceid_user) - responses_dict = {} - for answer in responses: - if not married and answer.question_id in married_questions: - responses_dict[answer.question.key] = '' - elif answer.value.strip('[').strip(']'): - responses_dict[answer.question.key] = answer.value - return responses_dict - - -def get_responses_from_db_grouped_by_steps(bceid_user, hide_failed_conditionals=False): +def get_data_for_user(bceid_user): """ - Group questions and responses by steps to which they belong - - `hide_failed_conditionals` goes through the responses after grouping and - tests their conditionality. If they fail, the response is blanked (this is - to hide conditional responses that are no longer applicable but haven't been - erased, mainly for the question review page). + Return a dictionary of {question_key: user_response_value} """ - married, married_questions, responses = __get_data(bceid_user) + responses = UserResponse.objects.filter(bceid_user=bceid_user) responses_dict = {} + for response in responses: + if response.value.strip('[').strip(']'): + responses_dict[response.question_id] = response.value - for step, questions in question_step_mapping.items(): + return responses_dict - lst = [] - step_responses = responses.filter(question_id__in=questions).exclude( - value__in=['', '[]', '[["",""]]']).order_by('question') - for answer in step_responses: - if not married and answer.question_id in married_questions: - value = '' +def get_step_responses(responses_by_key): + """ + Accepts a dictionary of {question_key: user_response_value} (from get_data_for_user) + Returns a dictionary of {step: {question_id: {question__name, question_id, value, error}}} + """ + responses_by_step = {} + for step in page_step_mapping.values(): + questions_dict = _get_questions_dict_set_for_step(step) + step_responses = [] + for question in questions_dict: + question_details = _get_question_details(question, questions_dict, responses_by_key) + if question_details['show']: + question_dict = questions_dict[question] + question_dict['value'] = question_details['value'] + question_dict['error'] = question_details['error'] + + step_responses.append(question_dict) + responses_by_step[step] = step_responses + return responses_by_step + + +def _get_questions_dict_set_for_step(step): + questions = Question.objects.filter(key__in=question_step_mapping[step]) + questions_dict = {} + for question in questions: + question_dict = { + 'question__conditional_target': question.conditional_target, + 'question__reveal_response': question.reveal_response, + 'question__name': question.name, + 'question__required': question.required, + 'question_id': question.key, + } + questions_dict[question.pk] = question_dict + return questions_dict + + +def _cleaned_response_value(response): + ignore_values = [None, '', '[]', '[["",""]]', '[["also known as",""]]'] + if response not in ignore_values: + return response + return None + + +def _condition_met(target_response, reveal_response): + # check whether using a numeric condition + numeric_condition_met = evaluate_numeric_condition(target_response, reveal_response) + if numeric_condition_met is None: + # handle special negation options. ex) '!NO' matches anything but 'NO' + if reveal_response.startswith('!'): + if target_response == "" or target_response.lower() == reveal_response[1:].lower(): + return False + elif str(target_response) != reveal_response: + return False + elif numeric_condition_met is False: + return False + return True + + +def _get_question_details(question, questions_dict, responses_by_key): + """ + Return details for a question given the set of question details and user responses. + value: The user's response to a question (or None if unanswered) + error: True if the question has an error (e.g. required but not answered) + show: False if the response shouldn't be displayed (e.g. don't show 'Also known as' name, but 'Does your spouse go by any other names' is NO) + """ + question_dict = questions_dict[question] + required = False + show = True + if question_dict["question__required"] == 'Required': + required = True + elif question_dict["question__required"] == 'Conditional': + target = question_dict["question__conditional_target"] + if target.startswith('determine_'): + # Look for the right function to evaluate conditional logic + derived_condition = getattr(conditional_logic, target) + if not derived_condition: + raise NotImplemented(target) + result = derived_condition(responses_by_key) + if result and _condition_met(result, question_dict["question__reveal_response"]): + required = True + else: + show = False + elif question in questions_dict: + target_response = responses_by_key.get(target) + if target_response and _condition_met(target_response, question_dict["question__reveal_response"]): + required = True else: - value = answer.value - - lst += [{'question__conditional_target': answer.question.conditional_target, - 'question__reveal_response': answer.question.reveal_response, - 'value': value, - 'question__name': answer.question.name, - 'question__required': answer.question.required, - 'question_id': answer.question.pk}] - - # This was added for DIV-514, where the user entered a name change for - # their spouse but then said 'no', they won't be changing their name. - # Since we don't blank related answers, we need to hide it dynamically. - # This only works for questions in the same step. - if hide_failed_conditionals: - values = {q['question_id']: q['value'] for q in lst} - for q in lst: - if q['question__required'] != 'Conditional': - continue - target = q['question__conditional_target'] - if target.startswith('['): - targets = target.strip('[]').split(',') - filtered_targets = [t for t in targets if t not in values] - # filtered_targets = list(filter(lambda t: t not in values, targets)) - if len(filtered_targets): - continue - - reveal_responses = dict(zip(targets, q['question__reveal_response'].strip('[]').split(','))) - present = [val for key, val in reveal_responses.items() if val != values[key]] - if len(present): - q['value'] = '' - continue - - if target not in values: - continue - numeric_condition = evaluate_numeric_condition(values[target], q['question__reveal_response']) - if numeric_condition is None: - if q['question__reveal_response'].startswith('!'): - if values[target] == "" or values[target] == q['question__reveal_response'][1:]: - q['value'] = '' - elif q['question__reveal_response'] and q['question__reveal_response'] != values[target]: - q['value'] = '' - elif numeric_condition is False: - q['value'] = '' - - responses_dict[step] = lst + show = False + + if show: + value = None + response = responses_by_key.get(question) + if response: + value = _cleaned_response_value(response) + error = required and not value + else: + value = None + error = None - return responses_dict + details = { + 'value': value, + 'error': error, + 'show': show + } + return details def get_responses_from_session(request): @@ -139,25 +177,3 @@ def copy_session_to_db(request, bceid_user): # clear the response from the session request.session[q.key] = None - - -def __get_data(bceid_user): - """ - Gets UserResponses from the database for a user, plus a boolean indicating - if the user is married or common-law, and a list of questions that only apply to - married couples - """ - COMMON_LAW = 'Living together in a marriage like relationship' - MARRIED = 'Legally married' - - responses = UserResponse.objects.filter(bceid_user=bceid_user).select_related('question') - married_status = responses.filter(question_id='married_marriage_like') - - if married_status.count() > 0: - married = married_status[0].value != COMMON_LAW - else: - married = False - - married_questions = list( - Question.objects.filter(reveal_response=MARRIED).values_list("key", flat=True)) - return married, married_questions, responses diff --git a/edivorce/apps/core/views/main.py b/edivorce/apps/core/views/main.py index 31c2b457..b9584461 100644 --- a/edivorce/apps/core/views/main.py +++ b/edivorce/apps/core/views/main.py @@ -1,4 +1,5 @@ import datetime +from copy import deepcopy from django.conf import settings from django.shortcuts import render, redirect @@ -7,11 +8,15 @@ from django.utils import timezone from edivorce.apps.core.utils.derived import get_derived_data from ..decorators import bceid_required, intercept from ..utils.question_step_mapping import list_of_registries, page_step_mapping -from ..utils.step_completeness import get_step_status, is_complete, get_formatted_incomplete_list +from ..utils.step_completeness import get_step_completeness, is_complete, get_formatted_incomplete_list from ..utils.template_step_order import template_step_order -from ..utils.user_response import get_responses_from_db, copy_session_to_db, \ - get_responses_from_db_grouped_by_steps, get_responses_from_session, \ - get_responses_from_session_grouped_by_steps +from ..utils.user_response import ( + get_data_for_user, + copy_session_to_db, + get_step_responses, + get_responses_from_session, + get_responses_from_session_grouped_by_steps, +) def home(request): @@ -40,10 +45,10 @@ def prequalification(request, step): if not request.user.is_authenticated: responses_dict = get_responses_from_session(request) else: - responses_dict = get_responses_from_db(request.user) + responses_dict = get_data_for_user(request.user) responses_dict['active_page'] = 'prequalification' - responses_by_step = get_responses_from_db_grouped_by_steps(request.user) - step_status, _ = get_step_status(responses_by_step) + responses_by_step = get_step_responses(responses_dict) + step_status, _ = get_step_completeness(responses_by_step) responses_dict['step_status'] = step_status return render(request, template_name=template, context=responses_dict) @@ -154,13 +159,14 @@ def overview(request): """ Dashboard: Process overview page. """ - responses_dict_by_step = get_responses_from_db_grouped_by_steps(request.user) + responses_dict = get_data_for_user(request.user) + responses_dict_by_step = get_step_responses(responses_dict) # Add step status dictionary - step_status, _ = get_step_status(responses_dict_by_step) + step_status, _ = get_step_completeness(responses_dict_by_step) responses_dict_by_step['step_status'] = step_status responses_dict_by_step['active_page'] = 'overview' - responses_dict_by_step['derived'] = get_derived_data(get_responses_from_db(request.user)) + responses_dict_by_step['derived'] = get_derived_data(responses_dict) response = render(request, 'overview.html', context=responses_dict_by_step) @@ -175,7 +181,7 @@ def dashboard_nav(request, nav_step): """ Dashboard: All other pages """ - responses_dict = get_responses_from_db(request.user) + responses_dict = get_data_for_user(request.user) responses_dict['active_page'] = nav_step template_name = 'dashboard/%s.html' % nav_step return render(request, template_name=template_name, context=responses_dict) @@ -189,19 +195,30 @@ def question(request, step, sub_step=None): sub_page_template = '_{}'.format(sub_step) if sub_step else '' template = 'question/%02d_%s%s.html' % (template_step_order[step], step, sub_page_template) - responses_dict_by_step = get_responses_from_db_grouped_by_steps(request.user, True) - step_status, missing_questions = get_step_status(responses_dict_by_step) if step == "review": - responses_dict = responses_dict_by_step - derived = get_derived_data(get_responses_from_db(request.user)) + responses_dict = get_data_for_user(request.user) + responses_dict_by_step = get_step_responses(responses_dict) + step_status, missing_questions = get_step_completeness(responses_dict_by_step) + derived = get_derived_data(responses_dict) + responses_dict = {} + + # Just for now (until showing missing questions in review is implemented) remove unanswered questions + for step, question_list in responses_dict_by_step.items(): + copy = deepcopy(question_list) + for question_dict in question_list: + if question_dict['value'] is None: + copy.remove(question_dict) + responses_dict[step] = copy else: + responses_dict = get_data_for_user(request.user) + responses_dict_by_step = get_step_responses(responses_dict) + step_status, missing_questions = get_step_completeness(responses_dict_by_step) question_step = page_step_mapping.get(step, step) show_errors = step_status.get(question_step) == 'Started' - responses_dict = get_responses_from_db(request.user) derived = get_derived_data(responses_dict) if show_errors: - for key in missing_questions.get(question_step): - responses_dict[key + '_error'] = True + for question_dict in missing_questions.get(question_step): + responses_dict[question_dict['question_id'] + '_error'] = True # Add step status dictionary responses_dict['step_status'] = step_status @@ -258,7 +275,7 @@ def intercept_page(request): input. """ template = 'question/%02d_%s.html' % (template_step_order['orders'], 'orders') - responses_dict = get_responses_from_db(request.user) + responses_dict = get_data_for_user(request.user) responses_dict['intercepted'] = True return render(request, template_name=template, context=responses_dict) diff --git a/edivorce/apps/core/views/pdf.py b/edivorce/apps/core/views/pdf.py index ab6acbe3..2ab62751 100644 --- a/edivorce/apps/core/views/pdf.py +++ b/edivorce/apps/core/views/pdf.py @@ -10,7 +10,7 @@ import requests from ..decorators import bceid_required from ..utils.derived import get_derived_data -from ..utils.user_response import get_responses_from_db +from ..utils.user_response import get_data_for_user EXHIBITS = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ'[::-1]) @@ -19,7 +19,7 @@ EXHIBITS = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ'[::-1]) def form(request, form_number): """ View for rendering PDF's and previews """ - responses = get_responses_from_db(request.user) + responses = get_data_for_user(request.user) if (form_number == '1' or form_number.startswith('37') or form_number.startswith('38') or diff --git a/edivorce/fixtures/Question.json b/edivorce/fixtures/Question.json index 223cadc4..3f8a4d26 100644 --- a/edivorce/fixtures/Question.json +++ b/edivorce/fixtures/Question.json @@ -395,7 +395,7 @@ "name": "Where were you married? Prov", "description": "For step 4, Form 1 2. Divorce section A, Form 52 Court orders that section", "summary_order": 38, - "required": "Required" + "required": "" }, "model": "core.question", "pk": "where_were_you_married_prov" @@ -504,15 +504,6 @@ "model": "core.question", "pk": "how_to_divide_property_debt" }, -{ - "fields": { - "name": "Claimant 1 and Claimant 2 ask for an order respecting an interest in property or for compensation instead of an interest in that property, as follows", - "description": "For step 7, Form 1 6. Property and debt", - "summary_order": 49 - }, - "model": "core.question", - "pk": "want_other_property_claims" -}, { "fields": { "name": "Please list any other property claims.", @@ -1219,8 +1210,8 @@ "description": "For Step 6, Your children - Income & expenses - Spouse Fact Sheet F", "summary_order": 0, "required": "Conditional", - "conditional_target": "claiming_undue_hardship", - "reveal_response": "YES" + "conditional_target": "determine_missing_undue_hardship_reasons", + "reveal_response": "True" }, "model": "core.question", "pk": "claimant_debts" @@ -1231,8 +1222,8 @@ "description": "For Step 6, Your children - Income & expenses - Spouse Fact Sheet F", "summary_order": 0, "required": "Conditional", - "conditional_target": "claiming_undue_hardship", - "reveal_response": "YES" + "conditional_target": "determine_missing_undue_hardship_reasons", + "reveal_response": "True" }, "model": "core.question", "pk": "claimant_expenses" @@ -1243,8 +1234,8 @@ "description": "For Step 6, Your children - Income & expenses - Spouse Fact Sheet F", "summary_order": 0, "required": "Conditional", - "conditional_target": "claiming_undue_hardship", - "reveal_response": "YES" + "conditional_target": "determine_missing_undue_hardship_reasons", + "reveal_response": "True" }, "model": "core.question", "pk": "supporting_non_dependents" @@ -1255,8 +1246,8 @@ "description": "For Step 6, Your children - Income & expenses - Spouse Fact Sheet F", "summary_order": 0, "required": "Conditional", - "conditional_target": "claiming_undue_hardship", - "reveal_response": "YES" + "conditional_target": "determine_missing_undue_hardship_reasons", + "reveal_response": "True" }, "model": "core.question", "pk": "supporting_dependents" @@ -1267,8 +1258,8 @@ "description": "For Step 6, Your children - Income & expenses - Spouse Fact Sheet F", "summary_order": 0, "required": "Conditional", - "conditional_target": "claiming_undue_hardship", - "reveal_response": "YES" + "conditional_target": "determine_missing_undue_hardship_reasons", + "reveal_response": "True" }, "model": "core.question", "pk": "supporting_disabled" @@ -1279,8 +1270,8 @@ "description": "For Step 6, Your children - Income & expenses - Spouse Fact Sheet F", "summary_order": 0, "required": "Conditional", - "conditional_target": "claiming_undue_hardship", - "reveal_response": "YES" + "conditional_target": "determine_missing_undue_hardship_reasons", + "reveal_response": "True" }, "model": "core.question", "pk": "undue_hardship" @@ -1290,9 +1281,7 @@ "name": "Income of Other Persons in Household", "description": "For Step 6, Your children - Income & expenses - Spouse Fact Sheet F", "summary_order": 0, - "required": "Conditional", - "conditional_target": "claiming_undue_hardship", - "reveal_response": "YES" + "required": "" }, "model": "core.question", "pk": "income_others" @@ -1540,7 +1529,8 @@ "summary_order": 0, "required": "Conditional", "conditional_target": "claimant_children", - "reveal_response": "" + "conditional_target": "determine_shared_custody", + "reveal_response": "True" }, "model": "core.question", "pk": "number_of_children" @@ -1551,8 +1541,8 @@ "description": "For Step 6, Your children - Your children - Fact Sheet B Shared Custody", "summary_order": 0, "required": "Conditional", - "conditional_target": "claimant_children", - "reveal_response": "" + "conditional_target": "determine_shared_custody", + "reveal_response": "True" }, "model": "core.question", "pk": "time_spent_with_you" @@ -1563,8 +1553,8 @@ "description": "For Step 6, Your children - Your children - Fact Sheet B Shared Custody", "summary_order": 0, "required": "Conditional", - "conditional_target": "claimant_children", - "reveal_response": "" + "conditional_target": "determine_shared_custody", + "reveal_response": "True" }, "model": "core.question", "pk": "time_spent_with_spouse" @@ -1575,8 +1565,8 @@ "description": "For Step 6, Your children - Your children - Fact Sheet B Shared Custody", "summary_order": 0, "required": "Conditional", - "conditional_target": "claimant_children", - "reveal_response": "" + "conditional_target": "determine_shared_custody", + "reveal_response": "True" }, "model": "core.question", "pk": "your_child_support_paid_b" @@ -1587,8 +1577,8 @@ "description": "For Step 6, Your children - Your children - Fact Sheet B Shared Custody", "summary_order": 0, "required": "Conditional", - "conditional_target": "claimant_children", - "reveal_response": "" + "conditional_target": "determine_shared_custody", + "reveal_response": "True" }, "model": "core.question", "pk": "your_spouse_child_support_paid_b" @@ -1779,7 +1769,8 @@ "description": "For Step 6, Your children - Income & expenses - Fact Sheet D Child(ren) 19 Years or Older", "summary_order": 0, "required": "Conditional", - "reveal_response": "" + "conditional_target": "determine_child_over_19_supported", + "reveal_response": "True" }, "model": "core.question", "pk": "agree_to_guideline_child_support_amount" @@ -1790,7 +1781,8 @@ "description": "For Step 6, Your children - Income & expenses - Fact Sheet D Child(ren) 19 Years or Older", "summary_order": 0, "required": "Conditional", - "reveal_response": "" + "conditional_target": "agree_to_guideline_child_support_amount", + "reveal_response": "NO" }, "model": "core.question", "pk": "appropriate_spouse_paid_child_support" @@ -1802,7 +1794,7 @@ "summary_order": 0, "required": "Conditional", "conditional_target": "agree_to_guideline_child_support_amount", - "reveal_response": "YES" + "reveal_response": "NO" }, "model": "core.question", "pk": "suggested_child_support" @@ -1823,7 +1815,8 @@ "description": "For Step 6, Your children - Income & expenses", "summary_order": 0, "required": "Conditional", - "reveal_response": "" + "conditional_target": "determine_sole_custody", + "reveal_response": "True" }, "model": "core.question", "pk": "payor_monthly_child_support_amount"