From fc66891c6a269e246e2d8677df1a8453bb160774 Mon Sep 17 00:00:00 2001 From: Justine Doutreloux Date: Tue, 10 Jan 2023 11:54:28 +0100 Subject: [PATCH 1/9] [ADD] survey_formio: add compatibility between survey and form.io This module adds the necessary function to generate a form.io compatible JSON that represents an odoo survey. It also adds the function that creates or updates a survey_user_input when given the JSON returned by form.io --- survey_formio/__init__.py | 1 + survey_formio/__manifest__.py | 15 + survey_formio/models/__init__.py | 2 + survey_formio/models/survey.py | 166 ++++++++++ survey_formio/models/survey_question.py | 187 ++++++++++++ survey_formio/readme/CONTRIBUTORS.rst | 1 + survey_formio/readme/DESCRIPTION.rst | 7 + survey_formio/readme/ROADMAP.rst | 5 + survey_formio/readme/USAGE.rst | 0 survey_formio/static/description/icon.png | Bin 0 -> 9455 bytes survey_formio/tests/__init__.py | 1 + survey_formio/tests/test_survey_formio.py | 350 ++++++++++++++++++++++ 12 files changed, 735 insertions(+) create mode 100644 survey_formio/__init__.py create mode 100644 survey_formio/__manifest__.py create mode 100644 survey_formio/models/__init__.py create mode 100644 survey_formio/models/survey.py create mode 100644 survey_formio/models/survey_question.py create mode 100644 survey_formio/readme/CONTRIBUTORS.rst create mode 100644 survey_formio/readme/DESCRIPTION.rst create mode 100644 survey_formio/readme/ROADMAP.rst create mode 100644 survey_formio/readme/USAGE.rst create mode 100644 survey_formio/static/description/icon.png create mode 100644 survey_formio/tests/__init__.py create mode 100644 survey_formio/tests/test_survey_formio.py diff --git a/survey_formio/__init__.py b/survey_formio/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/survey_formio/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/survey_formio/__manifest__.py b/survey_formio/__manifest__.py new file mode 100644 index 00000000..a55421be --- /dev/null +++ b/survey_formio/__manifest__.py @@ -0,0 +1,15 @@ +# Copyright 2023 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Survey Form.io", + "summary": """ + This module allows the generation of a form.io compatible JSON for a survey.""", + "version": "13.0.1.0.0", + "license": "AGPL-3", + "author": "Odoo Community Association (OCA), ACSONE SA/NV", + "website": "https://github.com/OCA/survey", + "depends": ["survey"], + "data": [], + "demo": [], +} diff --git a/survey_formio/models/__init__.py b/survey_formio/models/__init__.py new file mode 100644 index 00000000..603c5d09 --- /dev/null +++ b/survey_formio/models/__init__.py @@ -0,0 +1,2 @@ +from . import survey_question +from . import survey diff --git a/survey_formio/models/survey.py b/survey_formio/models/survey.py new file mode 100644 index 00000000..e1926ecc --- /dev/null +++ b/survey_formio/models/survey.py @@ -0,0 +1,166 @@ +# Copyright 2023 Acsone SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import json +from datetime import datetime + +import pytz + +from odoo import models + + +class SurveySurvey(models.Model): + _inherit = "survey.survey" + + def _get_formio_display(self): + """ + Returns a string representing the form.io display + Correct values are form, wizard or pdf. At the moment only + form is supported. + In the future use questions_layout to determine display. + """ + self.ensure_one() + return "form" + + def _get_survey_title(self): + """ + Returns a dict representing the title of the survey as a form.io + component. + """ + self.ensure_one() + return { + "label": "title", + "tag": "h1", + "content": self.title, + "key": "title", + "type": "htmlelement", + } + + def _get_formio_components(self): + """ + Pages are not supported yet. + + Returns a list of dict representing survey questions as form.io components + """ + self.ensure_one() + components = [] + components.append(self._get_survey_title()) + for question in self.question_ids: + component = question._get_formio_component() + if component: + components.append(component) + return components + + def _from_formio_datetime_to_odoo(self, datetime_str): + """ + Form.io datetimes are given in the user's timezone and with an offset. + Odoo wants naïve datetimes. + + Returns a naïve datetime as a string + + Parameters: + datetime_str: string representing a datetime with an offset + """ + # In the json given by form.io, the offset is formatted as +hh:mm + # To be able to use strptime we need it to be formated as +hhmm + + # We can't use the fromisoformat function because it's only available + # in Python v3.7+ and Odoo v13 is compatible with Python v3.6 + index = datetime_str.rfind(":") + datetime_str = datetime_str[:index] + datetime_str[index + 1 :] + utc_datetime = datetime.strptime( + datetime_str, "%Y-%m-%dT%H:%M:%S%z" + ).astimezone(pytz.UTC) + return utc_datetime.strftime("%Y-%m-%d %H:%M:%S") + + def _from_formio_multiple_choice_to_odoo( + self, question_id, multiple_choice_answers_dict + ): + """ + For a multiple choice question, the form.io answer has the + following format: + {"question_id": { + "answer_1_value": true/false + "answer_2_value": true/false + .... + }} + Odoo expects the following format: + {"question_id_answer_id": "answer_id"} + + Returns a dictionnary with the correct format. + + Parameters: + question_id: id of the survey.question + multiple_choice_answers_dict: dict given by form.io for a multiple choice question + """ + answers_dict = {} + for answer in multiple_choice_answers_dict.keys(): + answers_dict[question_id + "_" + answer[1:]] = answer[1:] + return answers_dict + + def generate_formio_json(self): + """ + Returns a form.io conform json representation of a survey. + """ + self.ensure_one() + display = self._get_formio_display() + title = self.title + components = self._get_formio_components() + + form_json = json.dumps( + {"display": display, "title": title, "components": components} + ) + return form_json + + def user_input_from_formio(self, formio_output, user_input_id=None): + """ + If no user_input_id is given, creates a user_input with the values in formio_output. + If there is a user_input_id, updates that user_input with the values in formio_output. + + Parameters: + formio_output: json given by form.io as a dictionnary + user_input_id: survey.user_input id + """ + if not user_input_id: + user_input_id = self.env["survey.user_input"].create({"survey_id": self.id}) + else: + user_input_id = self.env["survey.user_input"].browse(user_input_id) + form_io_answers = formio_output["data"] + for question in self.question_ids: + question_label = f"q{question.id}" + if form_io_answers.get(question_label): + if question.question_type == "datetime": + form_io_answers[ + question_label + ] = self._from_formio_datetime_to_odoo( + form_io_answers[question_label] + ) + if question.question_type == "date": + # We only need the date part of the string, not the hours + form_io_answers[question_label] = form_io_answers[question_label][ + :10 + ] + if question.question_type == "numerical_box": + # Odoo expects a string but form.io gives a number + # we have to cast it to avoid an error + form_io_answers[question_label] = str( + form_io_answers[question_label] + ) + if question.question_type == "simple_choice": + # Odoo expects a string but form.io gives a number + # we have to cast it to avoid an error + form_io_answers[question_label] = form_io_answers[question_label][ + 1: + ] + if question.question_type == "multiple_choice": + answers_dict = form_io_answers[question_label] + form_io_answers.pop(question_label) + form_io_answers.update( + self._from_formio_multiple_choice_to_odoo( + question_label, answers_dict + ) + ) + self.env["survey.user_input_line"].save_lines( + user_input_id.id, question, form_io_answers, question_label + ) + return user_input_id diff --git a/survey_formio/models/survey_question.py b/survey_formio/models/survey_question.py new file mode 100644 index 00000000..6c1e8d20 --- /dev/null +++ b/survey_formio/models/survey_question.py @@ -0,0 +1,187 @@ +# Copyright 2023 Acsone SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + + +from odoo import models + +ODOO_TO_FORM_IO_TYPES_DICT = { + "free_text": "textarea", + "textbox": "textfield", + "numerical_box": "number", + "date": "datetime", + "datetime": "datetime", + "simple_choice": "radio", + "multiple_choice": "selectboxes", +} + + +class SurveyQuestion(models.Model): + _inherit = "survey.question" + + def _get_formio_common_parameters(self): + """ + Returns a dict representing the common parameters for all form.io components. + + Form.io parameters: + - type : required + - key : required + - input : required but no corresponding parameter in survey. + Set to default form.io value + - label + - validate : only constr_mandatory supported at the moment, + will be handled in its own function + + See https://github.com/formio/formio.js/wiki/Components-JSON-Schema#common-parameters + for more information + """ + self.ensure_one() + return { + "type": ODOO_TO_FORM_IO_TYPES_DICT[self.question_type], + "key": f"q{self.id}", + "input": True, + "label": self.title, + } + + def _get_formio_validate_parameters(self): + """ + Returns a dict representing the validation parameters of a form.io component + + Form.io parameters: + - validate.required + """ + self.ensure_one() + return {"validate": {"required": self.constr_mandatory}} + + def _get_formio_textarea_parameters(self): + """ + Returns a dict containing parameters specific to a text area component + + Form.io parameters: + - rows : required but no corresponding parameter in survey. + Set to default form.io value + """ + self.ensure_one() + return { + "rows": 3, + } + + def _get_formio_textfield_parameters(self): + """ + Returns a dict containing parameters specific to a text field component + + This function does not take validation_length_min, validation_length_max + and validation_error_msg into account yet. + """ + self.ensure_one() + return {} + + def _get_formio_number_parameters(self): + """ + Returns a dict containing parameters specific to a number component + + This function does not take validation_min_float_value and validation_max_float_value + into account yet. + + Form.io parameters: + - validate.min : not supported at the moment + - validate.max : not supported at the moment + """ + self.ensure_one() + return {} + + def _get_formio_datetime_parameters(self, is_datetime=True): + """ + Returns a dict containing parameters specific to a datetime component + + This function does not take validation_min_date_time, validation_max_date_time + and validation_error_msg into account yet. + + Parameters: + is_datetime: boolean that determines if the time picker is available or not + + Form.io parameters: + - datePicker.minMode : required but no corresponding parameter in survey. + Set to default form.io value + - datePicker.maxMode : required but no corresponding parameter in survey. + Set to default form.io value + - timePicker.hourStep : required but no corresponding parameter in survey. + Set to default form.io value + - timePicker.minuteStep : required but no corresponding parameter in survey. + Set to default form.io value + """ + self.ensure_one() + return { + "datepicker": {"minMode": "day", "maxMode": "year"}, + "timePicker": {"hourStep": 1, "minuteStep": 1}, + "enableTime": is_datetime, + } + + def _get_formio_radio_parameters(self): + """ + Returns a dict containing parameters specific to a radio component + + This function does not take display_mode, column_nb and comments_allowed + into account yet. + + Form.io parameters: + - values: list of dict {"label": label, "value": value} + """ + self.ensure_one() + return { + "values": [ + {"label": label.value, "value": f"a{label.id}"} + for label in self.labels_ids + ] + } + + def _get_formio_selectboxes_parameters(self): + """ + Returns a dict containing parameters specific to a select boxes component + + This function does not take column_nb and comments_allowed into account yet. + + Form.io parameters: + - values: list of dict {"label": label, "value": value} + """ + self.ensure_one() + return { + "values": [ + {"label": label.value, "value": f"a{label.id}"} + for label in self.labels_ids + ] + } + + def _get_formio_component(self): + """ + Returns a dict representing a question as a valid formio component + """ + self.ensure_one() + + specific = {} + if self.question_type == "free_text": + specific = self._get_formio_textarea_parameters() + elif self.question_type == "textbox": + specific = self._get_formio_textfield_parameters() + elif self.question_type == "numerical_box": + specific = self._get_formio_number_parameters() + elif self.question_type == "date": + specific = self._get_formio_datetime_parameters(is_datetime=False) + elif self.question_type == "datetime": + specific = self._get_formio_datetime_parameters() + elif self.question_type == "simple_choice": + specific = self._get_formio_radio_parameters() + elif self.question_type == "multiple_choice": + specific = self._get_formio_selectboxes_parameters() + else: + # This question_type is not supported + return False + + common = self._get_formio_common_parameters() + validate = self._get_formio_validate_parameters() + # merge validate and specific dictionnaries + if specific.get("validate"): + specific["validate"].update(validate["validate"]) + else: + specific.update(validate) + common.update(specific) + return common diff --git a/survey_formio/readme/CONTRIBUTORS.rst b/survey_formio/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..f2a3c0b8 --- /dev/null +++ b/survey_formio/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Justine Doutreloux diff --git a/survey_formio/readme/DESCRIPTION.rst b/survey_formio/readme/DESCRIPTION.rst new file mode 100644 index 00000000..caac85b5 --- /dev/null +++ b/survey_formio/readme/DESCRIPTION.rst @@ -0,0 +1,7 @@ +This module adds compatibility bewtween `form.io ` and Survey. It adds a function on survey.survey that generates +a form.io compatible JSON that represents that survey. It also adds a function to generate a survey.user_input +from a JSON returned by form.io. +Use `survey.survey::generate_formio_json()` to get the json representing a survey that can be rendered using the form.io widget. +Use `survey.survey::user_input_from_formio()` to create a user_input with the answer from form.io. + +It does not work with question of type 'matrix' because there's no corresponding compoment in form.io. diff --git a/survey_formio/readme/ROADMAP.rst b/survey_formio/readme/ROADMAP.rst new file mode 100644 index 00000000..dbf69668 --- /dev/null +++ b/survey_formio/readme/ROADMAP.rst @@ -0,0 +1,5 @@ +* Take validation fields into account when generating the JSON, only constr_mandatory is used at the moment. +* Implement translations +* Use language settings for day and datetime parameters ? +* Implement function that warns user if his survey uses parameters not yet supported by this module +* Add validation for the JSON representing the answers diff --git a/survey_formio/readme/USAGE.rst b/survey_formio/readme/USAGE.rst new file mode 100644 index 00000000..e69de29b diff --git a/survey_formio/static/description/icon.png b/survey_formio/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/survey_formio/tests/__init__.py b/survey_formio/tests/__init__.py new file mode 100644 index 00000000..4bf9d30d --- /dev/null +++ b/survey_formio/tests/__init__.py @@ -0,0 +1 @@ +from . import test_survey_formio diff --git a/survey_formio/tests/test_survey_formio.py b/survey_formio/tests/test_survey_formio.py new file mode 100644 index 00000000..c94fa64b --- /dev/null +++ b/survey_formio/tests/test_survey_formio.py @@ -0,0 +1,350 @@ +# Copyright 2023 Acsone SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import json + +from odoo.tests import common + +from ..models.survey_question import ODOO_TO_FORM_IO_TYPES_DICT + + +class TestSurveyFormIo(common.SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.survey_public = cls.env["survey.survey"].create( + {"title": "Test Public Survey", "access_mode": "public", "state": "open"} + ) + cls.env["survey.question"].create( + cls._get_questions_vals_list(cls, cls.survey_public.id) + ) + cls.survey_private = cls.env["survey.survey"].create( + {"title": "Test Private Survey", "access_mode": "token", "state": "open"} + ) + cls.env["survey.question"].create( + cls._get_questions_vals_list(cls, cls.survey_private.id) + ) + + def _get_questions_vals_list(self, survey_id): + return [ + { + "title": "Test Free Text", + "survey_id": survey_id, + "sequence": 1, + "question_type": "free_text", + "constr_mandatory": True, + }, + { + "title": "Test TextBox", + "survey_id": survey_id, + "sequence": 2, + "question_type": "textbox", + }, + { + "title": "Test Numerical Box", + "survey_id": survey_id, + "sequence": 3, + "question_type": "numerical_box", + "constr_mandatory": True, + }, + { + "title": "Test Date", + "survey_id": survey_id, + "sequence": 4, + "question_type": "date", + }, + { + "title": "Test Datetime", + "survey_id": survey_id, + "sequence": 5, + "question_type": "datetime", + }, + { + "title": "Test Simple Choice", + "survey_id": survey_id, + "sequence": 2, + "question_type": "simple_choice", + "labels_ids": [ + (0, 0, {"value": "A"}), + (0, 0, {"value": "B"}), + (0, 0, {"value": "C"}), + ], + }, + { + "title": "Test Multiple choice", + "survey_id": survey_id, + "sequence": 2, + "question_type": "multiple_choice", + "labels_ids": [ + (0, 0, {"value": 1}), + (0, 0, {"value": 2}), + (0, 0, {"value": 3}), + ], + }, + ] + + def _get_answers_json(self, survey_id): + free_text_question = survey_id.question_ids.filtered( + lambda q: q.question_type == "free_text" + ) + text_box_question = survey_id.question_ids.filtered( + lambda q: q.question_type == "textbox" + ) + numerical_box_question = survey_id.question_ids.filtered( + lambda q: q.question_type == "numerical_box" + ) + date_question = survey_id.question_ids.filtered( + lambda q: q.question_type == "date" + ) + date_time_question = survey_id.question_ids.filtered( + lambda q: q.question_type == "datetime" + ) + simple_choice_question = survey_id.question_ids.filtered( + lambda q: q.question_type == "simple_choice" + ) + multiple_choice_question = survey_id.question_ids.filtered( + lambda q: q.question_type == "multiple_choice" + ) + return { + "data": { + f"q{free_text_question.id}": "test answer 1", + f"q{text_box_question.id}": "test answer 2", + f"q{numerical_box_question.id}": 10, + f"q{date_question.id}": "2023-02-01T00:00:00+01:00", + f"q{date_time_question.id}": "2023-02-01T12:00:00+01:00", + f"q{simple_choice_question.id}": ( + f"a{simple_choice_question.labels_ids[1].id}" + ), + f"q{multiple_choice_question.id}": { + f"a{multiple_choice_question.labels_ids[0].id}": True, + f"a{multiple_choice_question.labels_ids[1].id}": True, + f"a{multiple_choice_question.labels_ids[2].id}": False, + }, + } + } + + def _get_required_answers_json(self, survey_id): + free_text_question = survey_id.question_ids.filtered( + lambda q: q.question_type == "free_text" + ) + return {"data": {f"q{free_text_question.id}": "test answer 1"}} + + def test_survey_formio(self): + """ + Data: + - Two surveys: one public and one with an access token + - One question per question_type on those surveys + Test case: + - We use the generate_formio_json on both + - Add a question of type matrix to the first survey and + try to generate the json again. + Expected result: + - In both case the function should work with no error. + Each question should be represented by the correct component + - The json is generated without an error but the matrix question + is ignored + """ + public_survey_formio_dict = json.loads( + self.survey_public.generate_formio_json() + ) + self.assertEqual(public_survey_formio_dict["display"], "form") + self.assertEqual(public_survey_formio_dict["title"], self.survey_public.title) + self.assertEqual( + len(public_survey_formio_dict["components"]), + len(self.survey_public.question_ids) + 1, + ) # + 1 because the title is represented as a component too + for (i, component) in enumerate(public_survey_formio_dict["components"]): + if i == 0: + continue + question = self.survey_public.question_ids[i - 1] + self.assertEqual( + component["type"], ODOO_TO_FORM_IO_TYPES_DICT[question.question_type] + ) + self.assertEqual(component["key"], "q" + str(question.id)) + self.assertEqual( + component["validate"]["required"], question.constr_mandatory + ) + + private_survey_formio_dict = json.loads( + self.survey_private.generate_formio_json() + ) + self.assertEqual(private_survey_formio_dict["display"], "form") + self.assertEqual(private_survey_formio_dict["title"], self.survey_private.title) + self.assertEqual( + len(private_survey_formio_dict["components"]), + len(self.survey_private.question_ids) + 1, + ) # + 1 because the title is represented as a component too + for (i, component) in enumerate(private_survey_formio_dict["components"]): + if i == 0: + continue + question = self.survey_private.question_ids[i - 1] + self.assertEqual( + component["type"], ODOO_TO_FORM_IO_TYPES_DICT[question.question_type] + ) + self.assertEqual(component["key"], "q" + str(question.id)) + self.assertEqual(component["label"], question.title) + self.assertEqual( + component["validate"]["required"], question.constr_mandatory + ) + if question.question_type == "date": + self.assertFalse(component["enableTime"]) + if question.question_type == "datetime": + self.assertTrue(component["enableTime"]) + if question.question_type in ["simple_choice", "multiple_choice"]: + self.assertEqual(len(component["values"]), len(question.labels_ids)) + self.assertEqual( + [d["label"] for d in component["values"]], + question.labels_ids.mapped("value"), + ) + + self.env["survey.question"].create( + { + "survey_id": self.survey_public.id, + "title": "Test Matrix", + "question_type": "matrix", + "labels_ids": [ + (0, 0, {"value": "A"}), + (0, 0, {"value": "B"}), + (0, 0, {"value": "C"}), + ], + "labels_ids_2": [ + (0, 0, {"value": 1}), + (0, 0, {"value": 2}), + (0, 0, {"value": 3}), + ], + } + ) + public_survey_formio_dict = json.loads( + self.survey_public.generate_formio_json() + ) + self.assertEqual( + len(public_survey_formio_dict["components"]), + len(self.survey_public.question_ids), + ) + + def test_to_user_input_all_questions_answered(self): + """ + Data: + - Two surveys: one public and one with an access token + - A JSON representing answers to those surveys + Test case: + - We use the user_input_from_formio function on both surveys + with the JSON + Expected result: + - In both case the function should work with no error. + The answers should have the values given in the JSON + """ + self.assertEqual(self.survey_public.answer_count, 0) + answers = self._get_answers_json(self.survey_public) + self.survey_public.user_input_from_formio(answers) + self.assertEqual(self.survey_public.answer_count, 1) + user_input = self.survey_public.user_input_ids[0] + + # 7 questions answered but for the multiple choice there's a line per answer + self.assertEqual(len(user_input.user_input_line_ids), 9) + self.assertEqual( + user_input.user_input_line_ids.filtered( + lambda uil: uil.answer_type == "free_text" + ).value_free_text, + "test answer 1", + ) + self.assertEqual( + user_input.user_input_line_ids.filtered( + lambda uil: uil.answer_type == "number" + ).value_number, + 10, + ) + simple_choice_question = self.survey_public.question_ids.filtered( + lambda q: q.question_type == "simple_choice" + ) + self.assertEqual( + user_input.user_input_line_ids.filtered( + lambda uil: uil.question_id == simple_choice_question + ).value_suggested, + simple_choice_question.labels_ids[1], + ) + + self.assertEqual(self.survey_private.answer_count, 0) + answers = self._get_answers_json(self.survey_private) + self.survey_private.user_input_from_formio(answers) + self.assertEqual(self.survey_private.answer_count, 1) + + def test_to_user_input_only_required_questions_answered(self): + """ + Data: + - Two surveys: one public and one with an access token + - A JSON representing only the required answers to those surveys + Test case: + - We use the user_input_from_formio function on both surveys + with the JSON + Expected result: + - In both case the function should work with no error. + The answers should have the values given in the JSON + """ + self.assertEqual(self.survey_public.answer_count, 0) + answers = self._get_required_answers_json(self.survey_public) + self.survey_public.user_input_from_formio(answers) + self.assertEqual(self.survey_public.answer_count, 1) + user_input = self.survey_public.user_input_ids[0] + self.assertEqual(len(user_input.user_input_line_ids), 1) + self.assertEqual( + user_input.user_input_line_ids.filtered( + lambda uil: uil.answer_type == "free_text" + ).value_free_text, + "test answer 1", + ) + + self.assertEqual(self.survey_private.answer_count, 0) + answers = self._get_required_answers_json(self.survey_private) + self.survey_private.user_input_from_formio(answers) + self.assertEqual(self.survey_private.answer_count, 1) + + def test_to_user_input_with_user_input_id_answer(self): + """ + Data: + - Two surveys: one public and one with an access token + - Two user_inputs : one for each survey + - A JSON representing answers to those surveys + Test case: + - We use the user_input_from_formio function on both surveys + with the JSON and the user_input_id + Expected result: + - In both case the function should work with no error. + The answers should have the values given in the JSON + """ + public_user_input = self.survey_public._create_answer() + self.assertEqual(self.survey_public.answer_count, 1) + answers = self._get_answers_json(self.survey_public) + self.survey_public.user_input_from_formio(answers, public_user_input.id) + self.assertEqual(self.survey_public.answer_count, 1) + user_input = self.survey_public.user_input_ids[0] + + # 7 questions answered but for the multiple choice there's a line per answer + self.assertEqual(len(user_input.user_input_line_ids), 9) + self.assertEqual( + user_input.user_input_line_ids.filtered( + lambda uil: uil.answer_type == "free_text" + ).value_free_text, + "test answer 1", + ) + self.assertEqual( + user_input.user_input_line_ids.filtered( + lambda uil: uil.answer_type == "number" + ).value_number, + 10, + ) + simple_choice_question = self.survey_public.question_ids.filtered( + lambda q: q.question_type == "simple_choice" + ) + self.assertEqual( + user_input.user_input_line_ids.filtered( + lambda uil: uil.question_id == simple_choice_question + ).value_suggested, + simple_choice_question.labels_ids[1], + ) + + private_user_input = self.survey_private._create_answer() + self.assertEqual(self.survey_private.answer_count, 1) + answers = self._get_answers_json(self.survey_private) + self.survey_private.user_input_from_formio(answers, private_user_input.id) + self.assertEqual(self.survey_private.answer_count, 1) From 8390683d04822dec51c443911c3e17399958df6f Mon Sep 17 00:00:00 2001 From: oca-ci Date: Tue, 19 Sep 2023 15:38:25 +0000 Subject: [PATCH 2/9] [UPD] Update survey_formio.pot --- survey_formio/i18n/survey_formio.pot | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 survey_formio/i18n/survey_formio.pot diff --git a/survey_formio/i18n/survey_formio.pot b/survey_formio/i18n/survey_formio.pot new file mode 100644 index 00000000..9c419a26 --- /dev/null +++ b/survey_formio/i18n/survey_formio.pot @@ -0,0 +1,24 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * survey_formio +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: survey_formio +#: model:ir.model,name:survey_formio.model_survey_survey +msgid "Survey" +msgstr "" + +#. module: survey_formio +#: model:ir.model,name:survey_formio.model_survey_question +msgid "Survey Question" +msgstr "" From 4c1cfd7be689f84ff8c253e3b5d13792e6ae8ceb Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 19 Sep 2023 15:41:48 +0000 Subject: [PATCH 3/9] oca-github-bot post-merge updates --- survey_formio/README.rst | 91 ++++ survey_formio/__manifest__.py | 2 +- survey_formio/static/description/index.html | 437 ++++++++++++++++++++ 3 files changed, 529 insertions(+), 1 deletion(-) create mode 100644 survey_formio/README.rst create mode 100644 survey_formio/static/description/index.html diff --git a/survey_formio/README.rst b/survey_formio/README.rst new file mode 100644 index 00000000..2b478326 --- /dev/null +++ b/survey_formio/README.rst @@ -0,0 +1,91 @@ +============== +Survey Form.io +============== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:0bdfb6fd51f79a84f07f4ea8520e8ef26a62f0c2178274c749b4947e7f3b1ab7 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsurvey-lightgray.png?logo=github + :target: https://github.com/OCA/survey/tree/13.0/survey_formio + :alt: OCA/survey +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/survey-13-0/survey-13-0-survey_formio + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/survey&target_branch=13.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adds compatibility bewtween `form.io ` and Survey. It adds a function on survey.survey that generates +a form.io compatible JSON that represents that survey. It also adds a function to generate a survey.user_input +from a JSON returned by form.io. +Use `survey.survey::generate_formio_json()` to get the json representing a survey that can be rendered using the form.io widget. +Use `survey.survey::user_input_from_formio()` to create a user_input with the answer from form.io. + +It does not work with question of type 'matrix' because there's no corresponding compoment in form.io. + +**Table of contents** + +.. contents:: + :local: + +Known issues / Roadmap +====================== + +* Take validation fields into account when generating the JSON, only constr_mandatory is used at the moment. +* Implement translations +* Use language settings for day and datetime parameters ? +* Implement function that warns user if his survey uses parameters not yet supported by this module +* Add validation for the JSON representing the answers + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* ACSONE SA/NV + +Contributors +~~~~~~~~~~~~ + +* Justine Doutreloux + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/survey `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/survey_formio/__manifest__.py b/survey_formio/__manifest__.py index a55421be..a74a9683 100644 --- a/survey_formio/__manifest__.py +++ b/survey_formio/__manifest__.py @@ -5,7 +5,7 @@ "name": "Survey Form.io", "summary": """ This module allows the generation of a form.io compatible JSON for a survey.""", - "version": "13.0.1.0.0", + "version": "13.0.1.0.1", "license": "AGPL-3", "author": "Odoo Community Association (OCA), ACSONE SA/NV", "website": "https://github.com/OCA/survey", diff --git a/survey_formio/static/description/index.html b/survey_formio/static/description/index.html new file mode 100644 index 00000000..93b3c223 --- /dev/null +++ b/survey_formio/static/description/index.html @@ -0,0 +1,437 @@ + + + + + + +Survey Form.io + + + +
+

Survey Form.io

+ + +

Beta License: AGPL-3 OCA/survey Translate me on Weblate Try me on Runboat

+

This module adds compatibility bewtween form.io <https://formio.github.io/formio.js/app/builder> and Survey. It adds a function on survey.survey that generates +a form.io compatible JSON that represents that survey. It also adds a function to generate a survey.user_input +from a JSON returned by form.io. +Use survey.survey::generate_formio_json() to get the json representing a survey that can be rendered using the form.io widget. +Use survey.survey::user_input_from_formio() to create a user_input with the answer from form.io.

+

It does not work with question of type ‘matrix’ because there’s no corresponding compoment in form.io.

+

Table of contents

+ +
+

Known issues / Roadmap

+
    +
  • Take validation fields into account when generating the JSON, only constr_mandatory is used at the moment.
  • +
  • Implement translations <https://github.com/formio/formio.js/wiki/Translations>
  • +
  • Use language settings for day and datetime parameters ?
  • +
  • Implement function that warns user if his survey uses parameters not yet supported by this module
  • +
  • Add validation for the JSON representing the answers
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • ACSONE SA/NV
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/survey project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + From ce6206805f3959e3a49fc7ec761455bdc97dacab Mon Sep 17 00:00:00 2001 From: Justine Doutreloux Date: Thu, 28 Sep 2023 16:05:05 +0200 Subject: [PATCH 4/9] [FIX] survey_formio: fix multiple choice answers The dictionnary with the answer for a multiple choice question should only contain true answers. --- survey_formio/models/survey.py | 6 ++++++ survey_formio/tests/test_survey_formio.py | 25 +++++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/survey_formio/models/survey.py b/survey_formio/models/survey.py index e1926ecc..c7dbf19e 100644 --- a/survey_formio/models/survey.py +++ b/survey_formio/models/survey.py @@ -86,6 +86,9 @@ def _from_formio_multiple_choice_to_odoo( }} Odoo expects the following format: {"question_id_answer_id": "answer_id"} + Form.io returns all the answers with true or false but odoo doesn't + distinguish them like that. If the answer isn't selected, it shouldn't appear + in the dictionnary containing the answers. Returns a dictionnary with the correct format. @@ -94,6 +97,9 @@ def _from_formio_multiple_choice_to_odoo( multiple_choice_answers_dict: dict given by form.io for a multiple choice question """ answers_dict = {} + multiple_choice_answers_dict = { + key: value for (key, value) in multiple_choice_answers_dict.items() if value + } for answer in multiple_choice_answers_dict.keys(): answers_dict[question_id + "_" + answer[1:]] = answer[1:] return answers_dict diff --git a/survey_formio/tests/test_survey_formio.py b/survey_formio/tests/test_survey_formio.py index c94fa64b..6bcfb97b 100644 --- a/survey_formio/tests/test_survey_formio.py +++ b/survey_formio/tests/test_survey_formio.py @@ -241,7 +241,7 @@ def test_to_user_input_all_questions_answered(self): user_input = self.survey_public.user_input_ids[0] # 7 questions answered but for the multiple choice there's a line per answer - self.assertEqual(len(user_input.user_input_line_ids), 9) + self.assertEqual(len(user_input.user_input_line_ids), 8) self.assertEqual( user_input.user_input_line_ids.filtered( lambda uil: uil.answer_type == "free_text" @@ -264,6 +264,27 @@ def test_to_user_input_all_questions_answered(self): simple_choice_question.labels_ids[1], ) + multiple_choice_question = self.survey_public.question_ids.filtered( + lambda q: q.question_type == "multiple_choice" + ) + self.assertEqual( + len( + user_input.user_input_line_ids.filtered( + lambda uil: uil.question_id == multiple_choice_question + ) + ), + 2, + ) + self.assertEqual( + len( + user_input.user_input_line_ids.filtered( + lambda uil: uil.question_id == multiple_choice_question + and uil.value_suggested == multiple_choice_question.labels_ids[2] + ) + ), + 0, + ) + self.assertEqual(self.survey_private.answer_count, 0) answers = self._get_answers_json(self.survey_private) self.survey_private.user_input_from_formio(answers) @@ -320,7 +341,7 @@ def test_to_user_input_with_user_input_id_answer(self): user_input = self.survey_public.user_input_ids[0] # 7 questions answered but for the multiple choice there's a line per answer - self.assertEqual(len(user_input.user_input_line_ids), 9) + self.assertEqual(len(user_input.user_input_line_ids), 8) self.assertEqual( user_input.user_input_line_ids.filtered( lambda uil: uil.answer_type == "free_text" From caa1ec039c7546c7579255097d998d2f7b2175da Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Mon, 2 Oct 2023 08:03:15 +0000 Subject: [PATCH 5/9] [BOT] post-merge updates --- survey_formio/README.rst | 2 +- survey_formio/__manifest__.py | 2 +- survey_formio/static/description/index.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/survey_formio/README.rst b/survey_formio/README.rst index 2b478326..03c458e0 100644 --- a/survey_formio/README.rst +++ b/survey_formio/README.rst @@ -7,7 +7,7 @@ Survey Form.io !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:0bdfb6fd51f79a84f07f4ea8520e8ef26a62f0c2178274c749b4947e7f3b1ab7 + !! source digest: sha256:714149074a1397e2bb1acf4c7381ea336e8e80d999415e971fc92b1993ff5770 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/survey_formio/__manifest__.py b/survey_formio/__manifest__.py index a74a9683..5dc76287 100644 --- a/survey_formio/__manifest__.py +++ b/survey_formio/__manifest__.py @@ -5,7 +5,7 @@ "name": "Survey Form.io", "summary": """ This module allows the generation of a form.io compatible JSON for a survey.""", - "version": "13.0.1.0.1", + "version": "13.0.1.0.2", "license": "AGPL-3", "author": "Odoo Community Association (OCA), ACSONE SA/NV", "website": "https://github.com/OCA/survey", diff --git a/survey_formio/static/description/index.html b/survey_formio/static/description/index.html index 93b3c223..94e40ed0 100644 --- a/survey_formio/static/description/index.html +++ b/survey_formio/static/description/index.html @@ -367,7 +367,7 @@

Survey Form.io

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:0bdfb6fd51f79a84f07f4ea8520e8ef26a62f0c2178274c749b4947e7f3b1ab7 +!! source digest: sha256:714149074a1397e2bb1acf4c7381ea336e8e80d999415e971fc92b1993ff5770 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: AGPL-3 OCA/survey Translate me on Weblate Try me on Runboat

This module adds compatibility bewtween form.io <https://formio.github.io/formio.js/app/builder> and Survey. It adds a function on survey.survey that generates From a0189dc613cbde0ef61044187c48a18e479dd72c Mon Sep 17 00:00:00 2001 From: mymage Date: Fri, 27 Oct 2023 11:31:01 +0000 Subject: [PATCH 6/9] Added translation using Weblate (Italian) --- survey_formio/i18n/it.po | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 survey_formio/i18n/it.po diff --git a/survey_formio/i18n/it.po b/survey_formio/i18n/it.po new file mode 100644 index 00000000..54efc889 --- /dev/null +++ b/survey_formio/i18n/it.po @@ -0,0 +1,25 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * survey_formio +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#. module: survey_formio +#: model:ir.model,name:survey_formio.model_survey_survey +msgid "Survey" +msgstr "" + +#. module: survey_formio +#: model:ir.model,name:survey_formio.model_survey_question +msgid "Survey Question" +msgstr "" From 615628776468c41e8a1141ab1fadabb7e2fe7a9f Mon Sep 17 00:00:00 2001 From: mymage Date: Sun, 29 Oct 2023 09:40:39 +0000 Subject: [PATCH 7/9] Translated using Weblate (Italian) Currently translated at 100.0% (2 of 2 strings) Translation: survey-13.0/survey-13.0-survey_formio Translate-URL: https://translation.odoo-community.org/projects/survey-13-0/survey-13-0-survey_formio/it/ --- survey_formio/i18n/it.po | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/survey_formio/i18n/it.po b/survey_formio/i18n/it.po index 54efc889..95eb0ceb 100644 --- a/survey_formio/i18n/it.po +++ b/survey_formio/i18n/it.po @@ -6,20 +6,22 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 13.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: Automatically generated\n" +"PO-Revision-Date: 2023-10-29 09:40+0000\n" +"Last-Translator: mymage \n" "Language-Team: none\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" #. module: survey_formio #: model:ir.model,name:survey_formio.model_survey_survey msgid "Survey" -msgstr "" +msgstr "Sondaggio" #. module: survey_formio #: model:ir.model,name:survey_formio.model_survey_question msgid "Survey Question" -msgstr "" +msgstr "Domanda sondaggio" From b89fafed74667d3b078ead3e1e582bf677ead71b Mon Sep 17 00:00:00 2001 From: Thomas Binsfeld Date: Fri, 8 Mar 2024 09:12:53 +0100 Subject: [PATCH 8/9] [IMP] survey_formio: pre-commit execution --- setup/survey_formio/odoo/addons/survey_formio | 1 + setup/survey_formio/setup.py | 6 ++++++ 2 files changed, 7 insertions(+) create mode 120000 setup/survey_formio/odoo/addons/survey_formio create mode 100644 setup/survey_formio/setup.py diff --git a/setup/survey_formio/odoo/addons/survey_formio b/setup/survey_formio/odoo/addons/survey_formio new file mode 120000 index 00000000..cac9150e --- /dev/null +++ b/setup/survey_formio/odoo/addons/survey_formio @@ -0,0 +1 @@ +../../../../survey_formio \ No newline at end of file diff --git a/setup/survey_formio/setup.py b/setup/survey_formio/setup.py new file mode 100644 index 00000000..28c57bb6 --- /dev/null +++ b/setup/survey_formio/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) From ae7a79997f1d7ad859f402aa8ee57a8842729b83 Mon Sep 17 00:00:00 2001 From: Thomas Binsfeld Date: Fri, 8 Mar 2024 09:12:53 +0100 Subject: [PATCH 9/9] [MIG] survey_formio: Migration to 16.0 --- survey_formio/README.rst | 12 +-- survey_formio/__manifest__.py | 2 +- survey_formio/models/survey.py | 42 +++------- survey_formio/models/survey_question.py | 31 ++------ survey_formio/static/description/index.html | 8 +- survey_formio/tests/test_survey_formio.py | 85 +++++++++++---------- 6 files changed, 74 insertions(+), 106 deletions(-) diff --git a/survey_formio/README.rst b/survey_formio/README.rst index 03c458e0..2f602a7d 100644 --- a/survey_formio/README.rst +++ b/survey_formio/README.rst @@ -7,7 +7,7 @@ Survey Form.io !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:714149074a1397e2bb1acf4c7381ea336e8e80d999415e971fc92b1993ff5770 + !! source digest: sha256:52a994bb6ab6a31abeab4ae06d4c01ac0d2d8f38f6464449317c84f32d8f9e64 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png @@ -17,13 +17,13 @@ Survey Form.io :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsurvey-lightgray.png?logo=github - :target: https://github.com/OCA/survey/tree/13.0/survey_formio + :target: https://github.com/OCA/survey/tree/16.0/survey_formio :alt: OCA/survey .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/survey-13-0/survey-13-0-survey_formio + :target: https://translation.odoo-community.org/projects/survey-16-0/survey-16-0-survey_formio :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/survey&target_branch=13.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/survey&target_branch=16.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -56,7 +56,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -86,6 +86,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -This module is part of the `OCA/survey `_ project on GitHub. +This module is part of the `OCA/survey `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/survey_formio/__manifest__.py b/survey_formio/__manifest__.py index 5dc76287..df1edc59 100644 --- a/survey_formio/__manifest__.py +++ b/survey_formio/__manifest__.py @@ -5,7 +5,7 @@ "name": "Survey Form.io", "summary": """ This module allows the generation of a form.io compatible JSON for a survey.""", - "version": "13.0.1.0.2", + "version": "16.0.1.0.0", "license": "AGPL-3", "author": "Odoo Community Association (OCA), ACSONE SA/NV", "website": "https://github.com/OCA/survey", diff --git a/survey_formio/models/survey.py b/survey_formio/models/survey.py index c7dbf19e..deaaa76f 100644 --- a/survey_formio/models/survey.py +++ b/survey_formio/models/survey.py @@ -39,7 +39,6 @@ def _get_survey_title(self): def _get_formio_components(self): """ Pages are not supported yet. - Returns a list of dict representing survey questions as form.io components """ self.ensure_one() @@ -55,15 +54,12 @@ def _from_formio_datetime_to_odoo(self, datetime_str): """ Form.io datetimes are given in the user's timezone and with an offset. Odoo wants naïve datetimes. - Returns a naïve datetime as a string - Parameters: datetime_str: string representing a datetime with an offset """ # In the json given by form.io, the offset is formatted as +hh:mm # To be able to use strptime we need it to be formated as +hhmm - # We can't use the fromisoformat function because it's only available # in Python v3.7+ and Odoo v13 is compatible with Python v3.6 index = datetime_str.rfind(":") @@ -73,9 +69,7 @@ def _from_formio_datetime_to_odoo(self, datetime_str): ).astimezone(pytz.UTC) return utc_datetime.strftime("%Y-%m-%d %H:%M:%S") - def _from_formio_multiple_choice_to_odoo( - self, question_id, multiple_choice_answers_dict - ): + def _from_formio_multiple_choice_to_odoo(self, multiple_choice_answers_dict): """ For a multiple choice question, the form.io answer has the following format: @@ -84,25 +78,18 @@ def _from_formio_multiple_choice_to_odoo( "answer_2_value": true/false .... }} - Odoo expects the following format: - {"question_id_answer_id": "answer_id"} + Odoo expects the following format: [answer_id, answer_id, ...] Form.io returns all the answers with true or false but odoo doesn't distinguish them like that. If the answer isn't selected, it shouldn't appear - in the dictionnary containing the answers. - - Returns a dictionnary with the correct format. - + in the list containing the answers. + Returns a list with the correct format. Parameters: question_id: id of the survey.question multiple_choice_answers_dict: dict given by form.io for a multiple choice question """ - answers_dict = {} - multiple_choice_answers_dict = { - key: value for (key, value) in multiple_choice_answers_dict.items() if value - } - for answer in multiple_choice_answers_dict.keys(): - answers_dict[question_id + "_" + answer[1:]] = answer[1:] - return answers_dict + return [ + key[1:] for (key, value) in multiple_choice_answers_dict.items() if value + ] def generate_formio_json(self): """ @@ -112,7 +99,6 @@ def generate_formio_json(self): display = self._get_formio_display() title = self.title components = self._get_formio_components() - form_json = json.dumps( {"display": display, "title": title, "components": components} ) @@ -122,7 +108,6 @@ def user_input_from_formio(self, formio_output, user_input_id=None): """ If no user_input_id is given, creates a user_input with the values in formio_output. If there is a user_input_id, updates that user_input with the values in formio_output. - Parameters: formio_output: json given by form.io as a dictionnary user_input_id: survey.user_input id @@ -161,12 +146,9 @@ def user_input_from_formio(self, formio_output, user_input_id=None): if question.question_type == "multiple_choice": answers_dict = form_io_answers[question_label] form_io_answers.pop(question_label) - form_io_answers.update( - self._from_formio_multiple_choice_to_odoo( - question_label, answers_dict - ) - ) - self.env["survey.user_input_line"].save_lines( - user_input_id.id, question, form_io_answers, question_label - ) + form_io_answers[ + question_label + ] = self._from_formio_multiple_choice_to_odoo(answers_dict) + answer = form_io_answers.get(question_label) + user_input_id.save_lines(question, answer) return user_input_id diff --git a/survey_formio/models/survey_question.py b/survey_formio/models/survey_question.py index 6c1e8d20..b08cf46c 100644 --- a/survey_formio/models/survey_question.py +++ b/survey_formio/models/survey_question.py @@ -5,8 +5,8 @@ from odoo import models ODOO_TO_FORM_IO_TYPES_DICT = { - "free_text": "textarea", - "textbox": "textfield", + "text_box": "textarea", + "char_box": "textfield", "numerical_box": "number", "date": "datetime", "datetime": "datetime", @@ -21,7 +21,6 @@ class SurveyQuestion(models.Model): def _get_formio_common_parameters(self): """ Returns a dict representing the common parameters for all form.io components. - Form.io parameters: - type : required - key : required @@ -45,7 +44,6 @@ def _get_formio_common_parameters(self): def _get_formio_validate_parameters(self): """ Returns a dict representing the validation parameters of a form.io component - Form.io parameters: - validate.required """ @@ -55,7 +53,6 @@ def _get_formio_validate_parameters(self): def _get_formio_textarea_parameters(self): """ Returns a dict containing parameters specific to a text area component - Form.io parameters: - rows : required but no corresponding parameter in survey. Set to default form.io value @@ -68,7 +65,6 @@ def _get_formio_textarea_parameters(self): def _get_formio_textfield_parameters(self): """ Returns a dict containing parameters specific to a text field component - This function does not take validation_length_min, validation_length_max and validation_error_msg into account yet. """ @@ -78,10 +74,8 @@ def _get_formio_textfield_parameters(self): def _get_formio_number_parameters(self): """ Returns a dict containing parameters specific to a number component - This function does not take validation_min_float_value and validation_max_float_value into account yet. - Form.io parameters: - validate.min : not supported at the moment - validate.max : not supported at the moment @@ -92,13 +86,10 @@ def _get_formio_number_parameters(self): def _get_formio_datetime_parameters(self, is_datetime=True): """ Returns a dict containing parameters specific to a datetime component - This function does not take validation_min_date_time, validation_max_date_time and validation_error_msg into account yet. - Parameters: is_datetime: boolean that determines if the time picker is available or not - Form.io parameters: - datePicker.minMode : required but no corresponding parameter in survey. Set to default form.io value @@ -119,35 +110,31 @@ def _get_formio_datetime_parameters(self, is_datetime=True): def _get_formio_radio_parameters(self): """ Returns a dict containing parameters specific to a radio component - This function does not take display_mode, column_nb and comments_allowed into account yet. - Form.io parameters: - values: list of dict {"label": label, "value": value} """ self.ensure_one() return { "values": [ - {"label": label.value, "value": f"a{label.id}"} - for label in self.labels_ids + {"label": suggested_answer.value, "value": f"a{suggested_answer.id}"} + for suggested_answer in self.suggested_answer_ids ] } def _get_formio_selectboxes_parameters(self): """ Returns a dict containing parameters specific to a select boxes component - This function does not take column_nb and comments_allowed into account yet. - Form.io parameters: - values: list of dict {"label": label, "value": value} """ self.ensure_one() return { "values": [ - {"label": label.value, "value": f"a{label.id}"} - for label in self.labels_ids + {"label": suggested_answer.value, "value": f"a{suggested_answer.id}"} + for suggested_answer in self.suggested_answer_ids ] } @@ -156,11 +143,10 @@ def _get_formio_component(self): Returns a dict representing a question as a valid formio component """ self.ensure_one() - specific = {} - if self.question_type == "free_text": + if self.question_type == "text_box": specific = self._get_formio_textarea_parameters() - elif self.question_type == "textbox": + elif self.question_type == "char_box": specific = self._get_formio_textfield_parameters() elif self.question_type == "numerical_box": specific = self._get_formio_number_parameters() @@ -175,7 +161,6 @@ def _get_formio_component(self): else: # This question_type is not supported return False - common = self._get_formio_common_parameters() validate = self._get_formio_validate_parameters() # merge validate and specific dictionnaries diff --git a/survey_formio/static/description/index.html b/survey_formio/static/description/index.html index 94e40ed0..b20ffd35 100644 --- a/survey_formio/static/description/index.html +++ b/survey_formio/static/description/index.html @@ -367,9 +367,9 @@

Survey Form.io

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:714149074a1397e2bb1acf4c7381ea336e8e80d999415e971fc92b1993ff5770 +!! source digest: sha256:52a994bb6ab6a31abeab4ae06d4c01ac0d2d8f38f6464449317c84f32d8f9e64 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 OCA/survey Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/survey Translate me on Weblate Try me on Runboat

This module adds compatibility bewtween form.io <https://formio.github.io/formio.js/app/builder> and Survey. It adds a function on survey.survey that generates a form.io compatible JSON that represents that survey. It also adds a function to generate a survey.user_input from a JSON returned by form.io. @@ -404,7 +404,7 @@

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -feedback.

+feedback.

Do not contact contributors directly about support or help with technical issues.

@@ -428,7 +428,7 @@

Maintainers

OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

-

This module is part of the OCA/survey project on GitHub.

+

This module is part of the OCA/survey project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

diff --git a/survey_formio/tests/test_survey_formio.py b/survey_formio/tests/test_survey_formio.py index 6bcfb97b..e7f5dfb8 100644 --- a/survey_formio/tests/test_survey_formio.py +++ b/survey_formio/tests/test_survey_formio.py @@ -8,18 +8,24 @@ from ..models.survey_question import ODOO_TO_FORM_IO_TYPES_DICT -class TestSurveyFormIo(common.SavepointCase): +class TestSurveyFormIo(common.TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() cls.survey_public = cls.env["survey.survey"].create( - {"title": "Test Public Survey", "access_mode": "public", "state": "open"} + { + "title": "Test Public Survey", + "access_mode": "public", + } ) cls.env["survey.question"].create( cls._get_questions_vals_list(cls, cls.survey_public.id) ) cls.survey_private = cls.env["survey.survey"].create( - {"title": "Test Private Survey", "access_mode": "token", "state": "open"} + { + "title": "Test Private Survey", + "access_mode": "token", + } ) cls.env["survey.question"].create( cls._get_questions_vals_list(cls, cls.survey_private.id) @@ -31,14 +37,14 @@ def _get_questions_vals_list(self, survey_id): "title": "Test Free Text", "survey_id": survey_id, "sequence": 1, - "question_type": "free_text", + "question_type": "text_box", "constr_mandatory": True, }, { - "title": "Test TextBox", + "title": "Test char_box", "survey_id": survey_id, "sequence": 2, - "question_type": "textbox", + "question_type": "char_box", }, { "title": "Test Numerical Box", @@ -64,7 +70,7 @@ def _get_questions_vals_list(self, survey_id): "survey_id": survey_id, "sequence": 2, "question_type": "simple_choice", - "labels_ids": [ + "suggested_answer_ids": [ (0, 0, {"value": "A"}), (0, 0, {"value": "B"}), (0, 0, {"value": "C"}), @@ -75,7 +81,7 @@ def _get_questions_vals_list(self, survey_id): "survey_id": survey_id, "sequence": 2, "question_type": "multiple_choice", - "labels_ids": [ + "suggested_answer_ids": [ (0, 0, {"value": 1}), (0, 0, {"value": 2}), (0, 0, {"value": 3}), @@ -85,10 +91,10 @@ def _get_questions_vals_list(self, survey_id): def _get_answers_json(self, survey_id): free_text_question = survey_id.question_ids.filtered( - lambda q: q.question_type == "free_text" + lambda q: q.question_type == "text_box" ) text_box_question = survey_id.question_ids.filtered( - lambda q: q.question_type == "textbox" + lambda q: q.question_type == "char_box" ) numerical_box_question = survey_id.question_ids.filtered( lambda q: q.question_type == "numerical_box" @@ -113,19 +119,19 @@ def _get_answers_json(self, survey_id): f"q{date_question.id}": "2023-02-01T00:00:00+01:00", f"q{date_time_question.id}": "2023-02-01T12:00:00+01:00", f"q{simple_choice_question.id}": ( - f"a{simple_choice_question.labels_ids[1].id}" + f"a{simple_choice_question.suggested_answer_ids[1].id}" ), f"q{multiple_choice_question.id}": { - f"a{multiple_choice_question.labels_ids[0].id}": True, - f"a{multiple_choice_question.labels_ids[1].id}": True, - f"a{multiple_choice_question.labels_ids[2].id}": False, + f"a{multiple_choice_question.suggested_answer_ids[0].id}": True, + f"a{multiple_choice_question.suggested_answer_ids[1].id}": True, + f"a{multiple_choice_question.suggested_answer_ids[2].id}": False, }, } } def _get_required_answers_json(self, survey_id): free_text_question = survey_id.question_ids.filtered( - lambda q: q.question_type == "free_text" + lambda q: q.question_type == "text_box" ) return {"data": {f"q{free_text_question.id}": "test answer 1"}} @@ -164,7 +170,6 @@ def test_survey_formio(self): self.assertEqual( component["validate"]["required"], question.constr_mandatory ) - private_survey_formio_dict = json.loads( self.survey_private.generate_formio_json() ) @@ -191,23 +196,24 @@ def test_survey_formio(self): if question.question_type == "datetime": self.assertTrue(component["enableTime"]) if question.question_type in ["simple_choice", "multiple_choice"]: - self.assertEqual(len(component["values"]), len(question.labels_ids)) + self.assertEqual( + len(component["values"]), len(question.suggested_answer_ids) + ) self.assertEqual( [d["label"] for d in component["values"]], - question.labels_ids.mapped("value"), + question.suggested_answer_ids.mapped("value"), ) - self.env["survey.question"].create( { "survey_id": self.survey_public.id, "title": "Test Matrix", "question_type": "matrix", - "labels_ids": [ + "suggested_answer_ids": [ (0, 0, {"value": "A"}), (0, 0, {"value": "B"}), (0, 0, {"value": "C"}), ], - "labels_ids_2": [ + "matrix_row_ids": [ (0, 0, {"value": 1}), (0, 0, {"value": 2}), (0, 0, {"value": 3}), @@ -239,19 +245,18 @@ def test_to_user_input_all_questions_answered(self): self.survey_public.user_input_from_formio(answers) self.assertEqual(self.survey_public.answer_count, 1) user_input = self.survey_public.user_input_ids[0] - # 7 questions answered but for the multiple choice there's a line per answer self.assertEqual(len(user_input.user_input_line_ids), 8) self.assertEqual( user_input.user_input_line_ids.filtered( - lambda uil: uil.answer_type == "free_text" - ).value_free_text, + lambda uil: uil.answer_type == "text_box" + ).value_text_box, "test answer 1", ) self.assertEqual( user_input.user_input_line_ids.filtered( - lambda uil: uil.answer_type == "number" - ).value_number, + lambda uil: uil.answer_type == "numerical_box" + ).value_numerical_box, 10, ) simple_choice_question = self.survey_public.question_ids.filtered( @@ -260,10 +265,9 @@ def test_to_user_input_all_questions_answered(self): self.assertEqual( user_input.user_input_line_ids.filtered( lambda uil: uil.question_id == simple_choice_question - ).value_suggested, - simple_choice_question.labels_ids[1], + ).suggested_answer_id, + simple_choice_question.suggested_answer_ids[1], ) - multiple_choice_question = self.survey_public.question_ids.filtered( lambda q: q.question_type == "multiple_choice" ) @@ -279,12 +283,12 @@ def test_to_user_input_all_questions_answered(self): len( user_input.user_input_line_ids.filtered( lambda uil: uil.question_id == multiple_choice_question - and uil.value_suggested == multiple_choice_question.labels_ids[2] + and uil.suggested_answer_id + == multiple_choice_question.suggested_answer_ids[2] ) ), 0, ) - self.assertEqual(self.survey_private.answer_count, 0) answers = self._get_answers_json(self.survey_private) self.survey_private.user_input_from_formio(answers) @@ -310,11 +314,10 @@ def test_to_user_input_only_required_questions_answered(self): self.assertEqual(len(user_input.user_input_line_ids), 1) self.assertEqual( user_input.user_input_line_ids.filtered( - lambda uil: uil.answer_type == "free_text" - ).value_free_text, + lambda uil: uil.answer_type == "text_box" + ).value_text_box, "test answer 1", ) - self.assertEqual(self.survey_private.answer_count, 0) answers = self._get_required_answers_json(self.survey_private) self.survey_private.user_input_from_formio(answers) @@ -339,19 +342,18 @@ def test_to_user_input_with_user_input_id_answer(self): self.survey_public.user_input_from_formio(answers, public_user_input.id) self.assertEqual(self.survey_public.answer_count, 1) user_input = self.survey_public.user_input_ids[0] - # 7 questions answered but for the multiple choice there's a line per answer self.assertEqual(len(user_input.user_input_line_ids), 8) self.assertEqual( user_input.user_input_line_ids.filtered( - lambda uil: uil.answer_type == "free_text" - ).value_free_text, + lambda uil: uil.answer_type == "text_box" + ).value_text_box, "test answer 1", ) self.assertEqual( user_input.user_input_line_ids.filtered( - lambda uil: uil.answer_type == "number" - ).value_number, + lambda uil: uil.answer_type == "numerical_box" + ).value_numerical_box, 10, ) simple_choice_question = self.survey_public.question_ids.filtered( @@ -360,10 +362,9 @@ def test_to_user_input_with_user_input_id_answer(self): self.assertEqual( user_input.user_input_line_ids.filtered( lambda uil: uil.question_id == simple_choice_question - ).value_suggested, - simple_choice_question.labels_ids[1], + ).suggested_answer_id, + simple_choice_question.suggested_answer_ids[1], ) - private_user_input = self.survey_private._create_answer() self.assertEqual(self.survey_private.answer_count, 1) answers = self._get_answers_json(self.survey_private)