def get_rule_evaluator( *, language="en", schema: QuestionnaireSchema = None, answer_store: AnswerStore = AnswerStore(), list_store: ListStore = ListStore(), metadata: Optional[dict] = None, response_metadata: Mapping = None, location: Union[Location, RelationshipLocation] = Location(section_id="test-section", block_id="test-block"), routing_path_block_ids: Optional[list] = None, ): if not schema: schema = get_mock_schema() schema.is_repeating_answer = Mock(return_value=True) schema.get_default_answer = Mock(return_value=None) return RuleEvaluator( language=language, schema=schema, metadata=metadata or {}, response_metadata=response_metadata, answer_store=answer_store, list_store=list_store, location=location, routing_path_block_ids=routing_path_block_ids, )
def get_value_source_resolver( schema: QuestionnaireSchema = None, answer_store: AnswerStore = AnswerStore(), list_store: ListStore = ListStore(), metadata: Optional[dict] = None, location: Union[Location, RelationshipLocation] = Location(section_id="test-section", block_id="test-block"), list_item_id: Optional[str] = None, routing_path_block_ids: Optional[list] = None, use_default_answer=False, escape_answer_values=False, ): if not schema: schema = get_mock_schema() schema.is_repeating_answer = Mock(return_value=bool(list_item_id)) if not use_default_answer: schema.get_default_answer = Mock(return_value=None) return ValueSourceResolver( answer_store=answer_store, list_store=list_store, metadata=metadata, schema=schema, location=location, list_item_id=list_item_id, routing_path_block_ids=routing_path_block_ids, use_default_answer=use_default_answer, escape_answer_values=escape_answer_values, )
def test_bespoke_message_for_sum_validation(self): store = AnswerStore() answer_total = Answer(answer_id="total-answer", value=10) store.add_or_update(answer_total) with self.app_request_context(): schema = load_schema_from_name("test_sum_equal_validation_against_total") question_schema = QuestionnaireSchema.get_mutable_deepcopy( schema.get_block("breakdown-block").get("question") ) question_schema["validation"] = { "messages": {"TOTAL_SUM_NOT_EQUALS": "Test Message"} } form_data = MultiDict({"breakdown-1": "3", "breakdown-2": "5"}) form = generate_form( schema, question_schema, store, metadata=None, form_data=form_data ) with patch( "app.questionnaire.questionnaire_schema.QuestionnaireSchema.get_all_questions_for_block", return_value=[question_schema], ): form.validate() self.assertIn( form.question_errors["breakdown-question"], "Test Message" )
def render_placeholder(self, placeholder_data, list_item_id): placeholder_parser = PlaceholderParser( language=self._language, schema=self._schema, answer_store=self._answer_store, metadata=self._metadata, list_item_id=list_item_id, location=self._location, list_store=self._list_store, ) placeholder_data = QuestionnaireSchema.get_mutable_deepcopy( placeholder_data) if "text_plural" in placeholder_data: plural_schema = placeholder_data["text_plural"] count = self.get_plural_count(plural_schema["count"]) plural_form_key = get_plural_form_key(count, self._language) placeholder_data["text"] = plural_schema["forms"][plural_form_key] if "text" not in placeholder_data and "placeholders" not in placeholder_data: raise ValueError("No placeholder found to render") transformed_values = placeholder_parser( placeholder_data["placeholders"]) return placeholder_data["text"].format(**transformed_values)
def test_invalid_field_type_raises_on_invalid(answer_store, list_store): schema = QuestionnaireSchema({ "questionnaire_flow": { "type": "Linear", "options": { "summary": { "collapsible": False } }, } }) metadata = { "user_id": "789473423", "schema_name": "0000", "collection_exercise_sid": "test-sid", "period_id": "2016-02-01", "period_str": "2016-01-01", "ref_p_start_date": "2016-02-02", "ref_p_end_date": "2016-03-03", "ru_ref": "432423423423", "ru_name": "Apple", "return_by": "2016-07-07", "case_id": "1234567890", "case_ref": "1000000000000001", } response_metadata = {} value_source_resolver = ValueSourceResolver( answer_store=answer_store, list_store=list_store, metadata=metadata, response_metadata=response_metadata, schema=schema, location=None, list_item_id=None, escape_answer_values=False, ) rule_evaluator = RuleEvaluator( answer_store=answer_store, list_store=list_store, metadata=metadata, response_metadata=response_metadata, schema=schema, location=None, ) # Given invalid_field_type = "Football" # When / Then with pytest.raises(KeyError): get_field_handler( answer_schema={"type": invalid_field_type}, value_source_resolver=value_source_resolver, rule_evaluator=rule_evaluator, error_messages=error_messages, )
def confirmation_email_fulfilment_schema(): return QuestionnaireSchema({ "form_type": "H", "region_code": "GB-WLS", "submission": { "confirmation_email": True }, })
def __call__(self, placeholder_list: Sequence[Mapping]) -> Mapping: placeholder_list = QuestionnaireSchema.get_mutable_deepcopy( placeholder_list) for placeholder in placeholder_list: if placeholder["placeholder"] not in self._placeholder_map: self._placeholder_map[placeholder[ "placeholder"]] = self._parse_placeholder(placeholder) return self._placeholder_map
def test_load_schema_from_url_404(): load_schema_from_url.cache_clear() mock_schema = QuestionnaireSchema({}) responses.add(responses.GET, TEST_SCHEMA_URL, json=mock_schema.json, status=404) with pytest.raises(NotFound): load_schema_from_url(survey_url=TEST_SCHEMA_URL, language_code="en")
def test_load_schema_from_url_200(): load_schema_from_url.cache_clear() mock_schema = QuestionnaireSchema({}, language_code="cy") responses.add(responses.GET, TEST_SCHEMA_URL, json=mock_schema.json, status=200) loaded_schema = load_schema_from_url(survey_url=TEST_SCHEMA_URL, language_code="cy") assert loaded_schema.json == mock_schema.json assert loaded_schema.language_code == mock_schema.language_code
def test_load_schema_from_metadata_with_survey_url(): load_schema_from_url.cache_clear() metadata = {"survey_url": TEST_SCHEMA_URL, "language_code": "cy"} mock_schema = QuestionnaireSchema({}, language_code="cy") responses.add(responses.GET, TEST_SCHEMA_URL, json=mock_schema.json, status=200) loaded_schema = load_schema_from_metadata(metadata=metadata) assert loaded_schema.json == mock_schema.json assert loaded_schema.language_code == mock_schema.language_code
def __call__( self, placeholder_list: Sequence[Mapping] ) -> MutableMapping[str, Union[ValueSourceEscapedTypes, ValueSourceTypes]]: placeholder_list = QuestionnaireSchema.get_mutable_deepcopy( placeholder_list) for placeholder in placeholder_list: if placeholder["placeholder"] not in self._placeholder_map: self._placeholder_map[placeholder[ "placeholder"]] = self._parse_placeholder(placeholder) return self._placeholder_map
def add_relationships_unrelated_answers( answer_store: AnswerStore, list_store: ListStore, schema: QuestionnaireSchema, section_id: str, relationships_block: Mapping, answers_payload: AnswerStore, ) -> Optional[RelationshipStore]: relationships_answer_id = schema.get_first_answer_id_for_block( relationships_block["id"]) relationships_answer = answer_store.get_answer(relationships_answer_id) if not relationships_answer: return None relationship_store = RelationshipStore( relationships_answer.value) # type: ignore list_name = relationships_block["for_list"] unrelated_block = relationships_block["unrelated_block"] unrelated_block_id = unrelated_block["id"] unrelated_answer_id = schema.get_first_answer_id_for_block( unrelated_block_id) unrelated_no_answer_values = schema.get_unrelated_block_no_answer_values( unrelated_answer_id) relationship_router = RelationshipRouter( answer_store=answer_store, relationship_store=relationship_store, section_id=section_id, list_name=list_name, list_item_ids=list_store[list_name].items, relationships_block_id=relationships_block["id"], unrelated_block_id=unrelated_block_id, unrelated_answer_id=unrelated_answer_id, unrelated_no_answer_values=unrelated_no_answer_values, ) for location in relationship_router.path: if location.block_id == unrelated_block_id and ( unrelated_answer := answer_store.get_answer( unrelated_answer_id, list_item_id=location.list_item_id)): answers_payload.add_or_update(unrelated_answer)
def mock_schema(): schema = Mock( QuestionnaireSchema( { "questionnaire_flow": { "type": "Linear", "options": {"summary": {"collapsible": False}}, } } ) ) return schema
def render(self, dict_to_render, list_item_id): """ Transform the current schema json to a fully rendered dictionary """ dict_to_render = QuestionnaireSchema.get_mutable_deepcopy( dict_to_render) pointers = find_pointers_containing(dict_to_render, "placeholders") for pointer in pointers: rendered_text = self.render_pointer(dict_to_render, pointer, list_item_id) set_pointer(dict_to_render, pointer, rendered_text) return dict_to_render
def _add_link(self, summary, list_collector_block): if list_collector_block: return url_for( "questionnaire.block", list_name=summary["for_list"], block_id=list_collector_block["add_block"]["id"], return_to="section-summary", ) driving_question_block = QuestionnaireSchema.get_driving_question_for_list( self.section, summary["for_list"]) if driving_question_block: return url_for( "questionnaire.block", block_id=driving_question_block["id"], return_to="section-summary", )
def add_list_collector_answers( answer_store: AnswerStore, list_store: ListStore, schema: QuestionnaireSchema, list_collector_block: Mapping, answers_payload: AnswerStore, ) -> None: """Add answers from a ListCollector block. Output is added to the `answers_payload` argument.""" answers_ids_in_add_block = schema.get_answer_ids_for_list_items( list_collector_block["id"]) list_name = list_collector_block["for_list"] list_item_ids = list_store[list_name].items if answers_ids_in_add_block: for list_item_id in list_item_ids: for answer_id in answers_ids_in_add_block: answer = answer_store.get_answer(answer_id, list_item_id) if answer: answers_payload.add_or_update(answer)
def _add_link(self, summary, current_location, section, list_collector_block): routing_path = self._router.routing_path(section["id"], current_location.list_item_id) if list_collector_block["id"] in routing_path: return url_for( "questionnaire.block", list_name=summary["for_list"], block_id=list_collector_block["add_block"]["id"], return_to="section-summary", ) driving_question_block = QuestionnaireSchema.get_driving_question_for_list( section, summary["for_list"]) if driving_question_block: return url_for( "questionnaire.block", block_id=driving_question_block["id"], return_to="section-summary", )
def test_invalid_calculation_type(self): store = AnswerStore() answer_total = Answer(answer_id="total-answer", value=10) store.add_or_update(answer_total) with self.app_request_context(): schema = load_schema_from_name("test_sum_equal_validation_against_total") question_schema = QuestionnaireSchema.get_mutable_deepcopy( schema.get_block("breakdown-block").get("question") ) question_schema["calculations"] = [ { "calculation_type": "subtraction", "answer_id": "total-answer", "answers_to_calculate": ["breakdown-1", "breakdown-2"], "conditions": ["equals"], } ] form_data = MultiDict({"breakdown-1": "3", "breakdown-2": "5"}) form = generate_form( schema, question_schema, store, metadata=None, form_data=form_data ) with self.assertRaises(Exception) as ite: with patch( "app.questionnaire.questionnaire_schema.QuestionnaireSchema.get_all_questions_for_block", return_value=[question_schema], ): form.validate() self.assertEqual( "Invalid calculation_type: subtraction", str(ite.exception) )
def schema_feedback(): return QuestionnaireSchema({ "survey_id": survey_id, "data_version": data_version })
def convert_answers_to_payload_0_0_3( answer_store: AnswerStore, list_store: ListStore, schema: QuestionnaireSchema, full_routing_path: RoutingPath, ) -> list[Answer]: """ Convert answers into the data format below 'data': [ { 'value': 'Joe', 'answer_id': 'first-name', 'list_item_id': 'axkkdh' }, { 'value': 'Dimaggio', 'answer_id': 'last-name', 'list_item_id': 'axkkdh' }, { 'value': 'No', 'answer_id': 'do-you-live-here' } ] For list answers, this method will query the list store and get all answers from the add list item block. If there are multiple list collectors for one list, they will have the same answer_ids, and will not be duplicated. Returns: A list of answer dictionaries. """ answers_payload = AnswerStore() for routing_path in full_routing_path: for block_id in routing_path: if block := schema.get_block(block_id): block_type = block["type"] if block_type == "RelationshipCollector" and "unrelated_block" in block: add_relationships_unrelated_answers( answer_store=answer_store, list_store=list_store, schema=schema, section_id=routing_path.section_id, relationships_block=block, answers_payload=answers_payload, ) if schema.is_list_block_type( block_type) or schema.is_primary_person_block_type( block_type): add_list_collector_answers( answer_store=answer_store, list_store=list_store, schema=schema, list_collector_block=block, answers_payload=answers_payload, ) answer_ids = schema.get_answer_ids_for_block(block_id) answers_in_block = answer_store.get_answers_by_answer_id( answer_ids, list_item_id=routing_path.list_item_id) for answer_in_block in answers_in_block: answers_payload.add_or_update(answer_in_block)
def convert_answers_to_payload_0_0_1( metadata: MetadataType, response_metadata: MetadataType, answer_store: AnswerStore, list_store: ListStore, schema: QuestionnaireSchema, full_routing_path: RoutingPath, ) -> OrderedDict[str, Any]: """ Convert answers into the data format below list_item_id bound answers are not currently supported 'data': { '001': '01-01-2016', '002': '30-03-2016' } :param metadata: questionnaire metadata :param response_metadata: response metadata :param answer_store: questionnaire answers :param list_store: list store :param schema: QuestionnaireSchema class with populated schema json :param full_routing_path: a list of section routing paths followed in the questionnaire :return: data in a formatted form """ data = OrderedDict() for routing_path in full_routing_path: for block_id in routing_path: answer_ids = schema.get_answer_ids_for_block(block_id) answers_in_block = answer_store.get_answers_by_answer_id( answer_ids, routing_path.list_item_id) for answer_in_block in answers_in_block: answer_schema = None block = schema.get_block_for_answer_id( answer_in_block.answer_id) current_location = Location( block_id=block_id, section_id=routing_path.section_id, list_item_id=routing_path.list_item_id, ) question = choose_question_to_display( block, schema, metadata, response_metadata, answer_store, list_store, current_location=current_location, ) for answer in question["answers"]: if answer["id"] == answer_in_block.answer_id: answer_schema = answer value = answer_in_block.value if answer_schema is not None and value is not None: if answer_schema["type"] == "Checkbox": data.update( _get_checkbox_answer_data( answer_store, answer_schema, value # type: ignore )) elif "q_code" in answer_schema: answer_data = _encode_value(value) if answer_data is not None: data[answer_schema[ "q_code"]] = _format_downstream_answer( answer_schema["type"], answer_in_block.value, answer_data, ) return data
def address_questionnaire_schema(concatenation_type): return QuestionnaireSchema({ "sections": [{ "id": "address-section", "groups": [{ "blocks": [ { "type": "Question", "id": "what-is-your-address", "question": { "id": "what-is-your-address-question", "title": "What is your address?", "type": "General", "answers": [ { "id": "building", "label": "Building", "mandatory": False, "type": "TextField", "default": "Government Buildings", }, { "id": "address-line-1", "label": "Address Line 1", "mandatory": True, "type": "TextField", }, { "id": "address-line-2", "label": "Address Line 2", "mandatory": False, "type": "TextField", }, { "id": "address-line-3", "label": "Address Line 3", "mandatory": False, "type": "TextField", }, { "id": "town-city", "label": "Town/City", "mandatory": False, "type": "TextField", }, { "id": "county", "label": "County", "mandatory": False, "type": "TextField", }, { "id": "postcode", "label": "Postcode", "mandatory": False, "type": "TextField", }, { "id": "country", "label": "Country", "mandatory": False, "type": "TextField", }, ], "summary": { "concatenation_type": concatenation_type }, }, }, ], "id": "address-group", }], }] })
def is_enabled(schema: QuestionnaireSchema) -> bool: if submission_schema := schema.get_post_submission(): return submission_schema.get("confirmation_email", False)
def schema(): return QuestionnaireSchema({"post_submission": {"view_response": True}})
def is_enabled(schema: QuestionnaireSchema): return schema.get_submission().get("confirmation_email")