Exemplo n.º 1
0
    def setUp(self):
        super().setUp()

        self.location = Location('group_foo', 0, 'block_bar')
        self.schema = MagicMock(spec=QuestionnaireSchema)
        self.answer_store = MagicMock(spec=AnswerStore)
        self.questionnaire_store = MagicMock(spec=QuestionnaireStore,
                                             completed_blocks=[],
                                             answer_store=self.answer_store)
        self.answer_store_updater = AnswerStoreUpdater(
            self.location, self.schema, self.questionnaire_store)
        self.schema.location_requires_group_instance.return_value = False
Exemplo n.º 2
0
def _save_sign_out(routing_path, current_location, form, schema, answer_store,
                   metadata):
    questionnaire_store = get_questionnaire_store(current_user.user_id,
                                                  current_user.user_ik)

    block = _get_block_json(current_location, schema, answer_store, metadata)

    if form.validate():
        answer_store_updater = AnswerStoreUpdater(current_location, schema,
                                                  questionnaire_store)
        answer_store_updater.save_answers(form)

        questionnaire_store.remove_completed_blocks(location=current_location)
        questionnaire_store.add_or_update()

        logout_user()

        return redirect(url_for('session.get_sign_out'))

    context = _get_context(routing_path, block, current_location, schema, form)
    return _render_page(block['type'], context, current_location, schema,
                        answer_store, metadata, routing_path)
Exemplo n.º 3
0
def post_household_composition(routing_path, schema, metadata, answer_store,
                               **kwargs):  # pylint: disable=too-many-locals
    group_id = kwargs['group_id']
    current_location = Location(group_id, 0, 'household-composition')
    questionnaire_store = get_questionnaire_store(current_user.user_id,
                                                  current_user.user_ik)

    answer_store_updater = AnswerStoreUpdater(current_location, schema,
                                              questionnaire_store)
    answer_store_updater.remove_repeats_for_changed_household_answers(
        request.form.copy())

    disable_mandatory = any(x in request.form for x in [
        'action[add_answer]', 'action[remove_answer]', 'action[save_sign_out]'
    ])

    block = _get_block_json(current_location, schema, answer_store, metadata)

    form = post_form_for_location(schema,
                                  block,
                                  current_location,
                                  answer_store,
                                  metadata,
                                  request.form,
                                  disable_mandatory=disable_mandatory)

    form.validate(
    )  # call validate here to keep errors in the form object on the context
    context = _get_context(routing_path, block, current_location, schema, form)

    if 'action[add_answer]' in request.form:
        form.household.append_entry()

        return _render_page(block['type'], context, current_location, schema,
                            answer_store, metadata, routing_path)

    if 'action[remove_answer]' in request.form:
        index_to_remove = int(request.form.get('action[remove_answer]'))
        form.remove_person(index_to_remove)

        return _render_page(block['type'], context, current_location, schema,
                            answer_store, metadata, routing_path)

    if 'action[save_sign_out]' in request.form:
        response = _save_sign_out(routing_path, current_location, form, schema,
                                  answer_store, metadata)
        answer_store_updater.remove_empty_household_members()

        return response

    if form.validate():
        answer_store_updater.save_answers(form)

        metadata = get_metadata(current_user)
        next_location = path_finder.get_next_location(
            current_location=current_location)

        return redirect(next_location.url(metadata))

    return _render_page(block['type'], context, current_location, schema,
                        answer_store, metadata, routing_path)
Exemplo n.º 4
0
def post_block(
        routing_path,
        schema,
        metadata,
        collection_metadata,
        answer_store,
        eq_id,
        form_type,
        collection_id,
        group_id,
        # pylint: disable=too-many-locals
        group_instance,
        block_id):
    current_location = Location(group_id, group_instance, block_id)
    completeness = get_completeness(current_user)
    router = Router(schema, routing_path, completeness, current_location)

    if not router.can_access_location():
        next_location = router.get_next_location()
        return _redirect_to_location(collection_id, eq_id, form_type,
                                     next_location)

    block = _get_block_json(current_location, schema, answer_store, metadata)

    schema_context = _get_schema_context(routing_path, current_location,
                                         metadata, collection_metadata,
                                         answer_store, schema)

    rendered_block = renderer.render(block, **schema_context)

    form = _generate_wtf_form(request.form, rendered_block, current_location,
                              schema)

    if 'action[save_sign_out]' in request.form:
        return _save_sign_out(routing_path, current_location, form, schema,
                              answer_store, metadata)

    if 'action[sign_out]' in request.form:
        return redirect(url_for('session.get_sign_out'))

    if form.validate():
        _set_started_at_metadata_if_required(form, collection_metadata)
        questionnaire_store = get_questionnaire_store(current_user.user_id,
                                                      current_user.user_ik)
        answer_store_updater = AnswerStoreUpdater(current_location, schema,
                                                  questionnaire_store)
        answer_store_updater.save_answers(form)

        next_location = path_finder.get_next_location(
            current_location=current_location)

        if _is_end_of_questionnaire(block, next_location):
            return submit_answers(routing_path, eq_id, form_type, schema)

        return redirect(_next_location_url(next_location))

    context = build_view_context(block['type'], metadata, schema, answer_store,
                                 schema_context, rendered_block,
                                 current_location, form)

    return _render_page(block['type'], context, current_location, schema,
                        answer_store, metadata, routing_path)
Exemplo n.º 5
0
class TestAnswerStoreUpdater(unittest.TestCase):
    def setUp(self):
        super().setUp()

        self.location = Location('group_foo', 0, 'block_bar')
        self.schema = MagicMock(spec=QuestionnaireSchema)
        self.answer_store = MagicMock(spec=AnswerStore)
        self.questionnaire_store = MagicMock(spec=QuestionnaireStore,
                                             completed_blocks=[],
                                             answer_store=self.answer_store)
        self.answer_store_updater = AnswerStoreUpdater(
            self.location, self.schema, self.questionnaire_store)
        self.schema.location_requires_group_instance.return_value = False

    def test_save_answers_with_answer_data(self):
        self.location.block_id = 'household-composition'
        self.schema.get_group_dependencies.return_value = None
        self.schema.get_answer_ids_for_block.return_value = [
            'first-name', 'middle-names', 'last-name'
        ]

        answers = [
            Answer(group_instance=0,
                   group_instance_id='group-0',
                   answer_id='first-name',
                   answer_instance=0,
                   value='Joe'),
            Answer(group_instance=0,
                   group_instance_id='group-0',
                   answer_id='middle-names',
                   answer_instance=0,
                   value=''),
            Answer(group_instance=0,
                   group_instance_id='group-0',
                   answer_id='last-name',
                   answer_instance=0,
                   value='Bloggs'),
            Answer(group_instance=0,
                   group_instance_id='group-1',
                   answer_id='first-name',
                   answer_instance=1,
                   value='Bob'),
            Answer(group_instance=0,
                   group_instance_id='group-1',
                   answer_id='middle-names',
                   answer_instance=1,
                   value=''),
            Answer(group_instance=0,
                   group_instance_id='group-1',
                   answer_id='last-name',
                   answer_instance=1,
                   value='Seymour')
        ]

        form = MagicMock()
        form.serialise.return_value = answers

        self.answer_store_updater.save_answers(form)

        assert self.questionnaire_store.completed_blocks == [self.location]
        assert len(answers) == self.answer_store.add_or_update.call_count

        # answers should be passed straight through as Answer objects
        answer_calls = list(map(mock.call, answers))
        assert answer_calls in self.answer_store.add_or_update.call_args_list

    def test_save_answers_with_form_data(self):
        answer_id = 'answer'
        answer_value = '1000'

        self.schema.get_answer_ids_for_block.return_value = [answer_id]
        self.schema.get_group_dependencies.return_value = None

        form = MagicMock(spec=QuestionnaireForm,
                         data={answer_id: answer_value})

        self.answer_store_updater.save_answers(form)

        assert self.questionnaire_store.completed_blocks == [self.location]
        assert self.answer_store.add_or_update.call_count == 1

        created_answer = self.answer_store.add_or_update.call_args[0][0]
        assert created_answer.__dict__ == {
            'group_instance': 0,
            'group_instance_id': None,
            'answer_id': answer_id,
            'answer_instance': 0,
            'value': answer_value
        }

    def test_save_answers_stores_specific_group(self):
        answer_id = 'answer'
        answer_value = '1000'

        self.location.group_instance = 1
        self.schema.get_answer_ids_for_block.return_value = [answer_id]
        self.schema.get_group_dependencies.return_value = None

        form = MagicMock(spec=QuestionnaireForm,
                         data={answer_id: answer_value})

        self.answer_store_updater.save_answers(form)

        assert self.questionnaire_store.completed_blocks == [self.location]
        assert self.answer_store.add_or_update.call_count == 1

        created_answer = self.answer_store.add_or_update.call_args[0][0]
        assert created_answer.__dict__ == {
            'group_instance': self.location.group_instance,
            'group_instance_id': None,
            'answer_id': answer_id,
            'answer_instance': 0,
            'value': answer_value
        }

    def test_save_answers_data_with_default_value(self):
        answer_id = 'answer'
        default_value = 0

        self.schema.get_answer_ids_for_block.return_value = [answer_id]
        self.schema.get_answer.return_value = {'default': default_value}

        # No answer given so will use schema defined default
        form_data = {answer_id: None}

        form = MagicMock(spec=QuestionnaireForm, data=form_data)

        self.answer_store_updater.save_answers(form)

        assert self.questionnaire_store.completed_blocks == [self.location]
        assert self.answer_store.add_or_update.call_count == 1

        created_answer = self.answer_store.add_or_update.call_args[0][0]
        assert created_answer.__dict__ == {
            'group_instance': 0,
            'group_instance_id': None,
            'answer_id': answer_id,
            'answer_instance': 0,
            'value': default_value
        }

    def test_remove_empty_household_members_from_answer_store(self):
        empty_household_answers = [{
            'answer_id': 'first-name',
            'group_instance_id': None,
            'group_instance': 0,
            'answer_instance': 0,
            'value': ''
        }, {
            'answer_id': 'middle-names',
            'group_instance_id': None,
            'group_instance': 0,
            'answer_instance': 0,
            'value': ''
        }, {
            'answer_id': 'last-name',
            'group_instance_id': None,
            'group_instance': 0,
            'answer_instance': 0,
            'value': ''
        }, {
            'answer_id': 'first-name',
            'group_instance_id': None,
            'group_instance': 0,
            'answer_instance': 1,
            'value': ''
        }, {
            'answer_id': 'middle-names',
            'group_instance_id': None,
            'group_instance': 0,
            'answer_instance': 1,
            'value': ''
        }, {
            'answer_id': 'last-name',
            'group_instance_id': None,
            'group_instance': 0,
            'answer_instance': 1,
            'value': ''
        }]

        self.schema.get_answer_ids_for_block.return_value = [
            'first-name', 'middle-names', 'last-name'
        ]
        self.answer_store.filter.return_value = iter(empty_household_answers)

        self.answer_store_updater.remove_empty_household_members()

        remove_answer_calls = [
            call(answer_ids=['first-name', 'middle-names', 'last-name'],
                 answer_instance=0),
            call(answer_ids=['first-name', 'middle-names', 'last-name'],
                 answer_instance=1)
        ]

        # both instances of the answer should be removed
        assert remove_answer_calls in self.answer_store.remove.call_args_list
        assert self.answer_store.remove.call_count == 2

    def test_remove_empty_household_members_values_entered_are_stored(self):
        household_answers = [
            # Answered
            {
                'answer_id': 'first-name',
                'group_instance_id': None,
                'group_instance': 0,
                'answer_instance': 0,
                'value': 'Joe'
            },
            {
                'answer_id': 'middle-names',
                'group_instance_id': None,
                'group_instance': 0,
                'answer_instance': 0,
                'value': ''
            },
            {
                'answer_id': 'last-name',
                'group_instance_id': None,
                'group_instance': 0,
                'answer_instance': 0,
                'value': 'Bloggs'
            },

            # Unanswered
            {
                'answer_id': 'first-name',
                'group_instance_id': None,
                'group_instance': 0,
                'answer_instance': 1,
                'value': ''
            },
            {
                'answer_id': 'middle-names',
                'group_instance_id': None,
                'group_instance': 0,
                'answer_instance': 1,
                'value': ''
            },
            {
                'answer_id': 'last-name',
                'group_instance_id': None,
                'group_instance': 0,
                'answer_instance': 1,
                'value': ''
            }
        ]

        self.schema.get_answer_ids_for_block.return_value = [
            'first-name', 'middle-names', 'last-name'
        ]
        self.answer_store.filter.return_value = iter(household_answers)

        self.answer_store_updater.remove_empty_household_members()

        # only the second instance of the answer should be removed
        assert self.answer_store.remove.call_count == 1

        remove_answer_calls = [
            call(answer_ids=['first-name', 'middle-names', 'last-name'],
                 answer_instance=1)
        ]
        assert remove_answer_calls in self.answer_store.remove.call_args_list

    def test_remove_empty_household_members_partial_answers_are_stored(self):
        self.location.block_id = 'household-composition'
        self.schema.get_group_dependencies.return_value = None

        household_answers = [
            # Answered
            {
                'answer_id': 'first-name',
                'group_instance_id': None,
                'group_instance': 0,
                'answer_instance': 0,
                'value': 'Joe'
            },
            {
                'answer_id': 'middle-names',
                'group_instance_id': None,
                'group_instance': 0,
                'answer_instance': 0,
                'value': 'J'
            },
            {
                'answer_id': 'last-name',
                'group_instance_id': None,
                'group_instance': 0,
                'answer_instance': 0,
                'value': 'Bloggs'
            },

            # Partially answered
            {
                'answer_id': 'first-name',
                'group_instance_id': None,
                'group_instance': 0,
                'answer_instance': 1,
                'value': ''
            },
            {
                'answer_id': 'middle-names',
                'group_instance_id': None,
                'group_instance': 0,
                'answer_instance': 1,
                'value': ''
            },
            {
                'answer_id': 'last-name',
                'group_instance_id': None,
                'group_instance': 0,
                'answer_instance': 1,
                'value': 'Last name only'
            },
            {
                'answer_id': 'first-name',
                'group_instance_id': None,
                'group_instance': 0,
                'answer_instance': 2,
                'value': 'First name only'
            },
            {
                'answer_id': 'middle-names',
                'group_instance_id': None,
                'group_instance': 0,
                'answer_instance': 2,
                'value': ''
            },
            {
                'answer_id': 'last-name',
                'group_instance_id': None,
                'group_instance': 0,
                'answer_instance': 2,
                'value': ''
            }
        ]
        self.answer_store.filter.return_value = iter(household_answers)
        self.schema.get_answer_ids_for_block.return_value = [
            'first-name', 'middle-names', 'last-name'
        ]

        self.answer_store_updater.remove_empty_household_members()

        # no answers should be removed
        assert self.answer_store.remove.called is False

    def test_remove_empty_household_members_middle_name_only_not_stored(self):
        household_answer = [{
            'answer_id': 'first-name',
            'group_instance_id': None,
            'group_instance': 0,
            'answer_instance': 0,
            'value': ''
        }, {
            'answer_id': 'middle-names',
            'group_instance_id': None,
            'group_instance': 0,
            'answer_instance': 0,
            'value': 'should not be saved'
        }, {
            'answer_id': 'last-name',
            'group_instance_id': None,
            'group_instance': 0,
            'answer_instance': 0,
            'value': ''
        }]

        self.schema.get_answer_ids_for_block.return_value = [
            'first-name', 'middle-names', 'last-name'
        ]
        self.answer_store.filter.return_value = iter(household_answer)

        self.answer_store_updater.remove_empty_household_members()

        # partial answer should be removed
        assert self.answer_store.remove.call_count == 1

        remove_answer_calls = [
            call(answer_ids=['first-name', 'middle-names', 'last-name'],
                 answer_instance=0)
        ]
        assert remove_answer_calls in self.answer_store.remove.call_args_list

    def test_save_answers_removes_completed_block_for_dependencies(self):
        parent_id, dependent_answer_id = 'parent_answer', 'dependent_answer'
        self.location = parent_location = Location('group', 0, 'min-block')
        dependent_location = Location('group', 0, 'dependent-block')

        self.questionnaire_store.completed_blocks = [
            parent_location, dependent_location
        ]
        self.schema.get_answer_ids_for_block.return_value = [parent_id]
        self.schema.answer_dependencies = {parent_id: [dependent_answer_id]}
        self.schema.get_block.return_value = {
            'id': dependent_location.block_id,
            'parent_id': dependent_location.group_id
        }

        # rotate the hash every time get_hash() is called to simulate the stored answer changing
        self.answer_store.get_hash.side_effect = ['first_hash', 'second_hash']

        form = MagicMock(spec=QuestionnaireForm, data={parent_id: '10'})

        self.schema.get_group_dependencies.return_value = None

        self.answer_store_updater.save_answers(form)

        assert self.answer_store.add_or_update.call_count == 1
        assert self.answer_store.remove.called is False

        self.questionnaire_store.remove_completed_blocks.assert_called_with(
            location=dependent_location)

        created_answer = self.answer_store.add_or_update.call_args[0][0]
        assert created_answer.__dict__ == {
            'group_instance': 0,
            'group_instance_id': None,
            'answer_id': parent_id,
            'answer_instance': 0,
            'value': '10'
        }

    def test_save_answers_removes_completed_block_for_dependencies_repeating_on_non_repeating_answer(
            self):
        """
        Tests that all dependent completed blocks are removed across all repeating groups when
        parent answer is not in a repeating group
        """
        parent_id, dependent_answer_id = 'parent_answer', 'dependent_answer'
        self.location = parent_location = Location('group', 0, 'min-block')
        dependent_location = Location('group', 0, 'dependent-block')

        self.questionnaire_store.completed_blocks = [
            parent_location, dependent_location
        ]
        self.schema.get_answer_ids_for_block.return_value = [parent_id]
        self.schema.answer_dependencies = {parent_id: [dependent_answer_id]}
        self.schema.get_block.return_value = {
            'id': dependent_location.block_id,
            'parent_id': dependent_location.group_id
        }

        # the dependent answer is in a repeating group, the parent is not
        self.schema.answer_is_in_repeating_group = lambda _answer_id: _answer_id == dependent_answer_id

        # rotate the hash every time get_hash() is called to simulate the stored answer changing
        self.answer_store.get_hash.side_effect = ['first_hash', 'second_hash']

        form = MagicMock(spec=QuestionnaireForm, data={parent_id: '10'})

        self.answer_store_updater.save_answers(form)

        self.questionnaire_store.remove_completed_blocks.assert_called_with(
            group_id=dependent_location.group_id,
            block_id=dependent_location.block_id)

        assert self.answer_store.add_or_update.call_count == 1
        assert self.answer_store.remove.called is False

        created_answer = self.answer_store.add_or_update.call_args[0][0]
        assert created_answer.__dict__ == {
            'group_instance': 0,
            'group_instance_id': None,
            'answer_id': parent_id,
            'answer_instance': 0,
            'value': '10'
        }