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, +) diff --git a/survey_formio/README.rst b/survey_formio/README.rst new file mode 100644 index 00000000..2f602a7d --- /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:52a994bb6ab6a31abeab4ae06d4c01ac0d2d8f38f6464449317c84f32d8f9e64 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/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-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=16.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/__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..df1edc59 --- /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": "16.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/i18n/it.po b/survey_formio/i18n/it.po new file mode 100644 index 00000000..95eb0ceb --- /dev/null +++ b/survey_formio/i18n/it.po @@ -0,0 +1,27 @@ +# 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" +"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 "Sondaggio" + +#. module: survey_formio +#: model:ir.model,name:survey_formio.model_survey_question +msgid "Survey Question" +msgstr "Domanda sondaggio" 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 "" 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..deaaa76f --- /dev/null +++ b/survey_formio/models/survey.py @@ -0,0 +1,154 @@ +# 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, 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: [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 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 + """ + return [ + key[1:] for (key, value) in multiple_choice_answers_dict.items() if value + ] + + 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[ + 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 new file mode 100644 index 00000000..b08cf46c --- /dev/null +++ b/survey_formio/models/survey_question.py @@ -0,0 +1,172 @@ +# 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 = { + "text_box": "textarea", + "char_box": "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": 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": suggested_answer.value, "value": f"a{suggested_answer.id}"} + for suggested_answer in self.suggested_answer_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 == "text_box": + specific = self._get_formio_textarea_parameters() + elif self.question_type == "char_box": + 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 00000000..3a0328b5 Binary files /dev/null and b/survey_formio/static/description/icon.png differ diff --git a/survey_formio/static/description/index.html b/survey_formio/static/description/index.html new file mode 100644 index 00000000..b20ffd35 --- /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.

+
+
+
+ + 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..e7f5dfb8 --- /dev/null +++ b/survey_formio/tests/test_survey_formio.py @@ -0,0 +1,372 @@ +# 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.TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.survey_public = cls.env["survey.survey"].create( + { + "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", + } + ) + 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": "text_box", + "constr_mandatory": True, + }, + { + "title": "Test char_box", + "survey_id": survey_id, + "sequence": 2, + "question_type": "char_box", + }, + { + "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", + "suggested_answer_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", + "suggested_answer_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 == "text_box" + ) + text_box_question = survey_id.question_ids.filtered( + lambda q: q.question_type == "char_box" + ) + 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.suggested_answer_ids[1].id}" + ), + f"q{multiple_choice_question.id}": { + 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 == "text_box" + ) + 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.suggested_answer_ids) + ) + self.assertEqual( + [d["label"] for d in component["values"]], + question.suggested_answer_ids.mapped("value"), + ) + self.env["survey.question"].create( + { + "survey_id": self.survey_public.id, + "title": "Test Matrix", + "question_type": "matrix", + "suggested_answer_ids": [ + (0, 0, {"value": "A"}), + (0, 0, {"value": "B"}), + (0, 0, {"value": "C"}), + ], + "matrix_row_ids": [ + (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), 8) + self.assertEqual( + user_input.user_input_line_ids.filtered( + 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 == "numerical_box" + ).value_numerical_box, + 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 + ).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" + ) + 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.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) + 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 == "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) + 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), 8) + self.assertEqual( + user_input.user_input_line_ids.filtered( + 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 == "numerical_box" + ).value_numerical_box, + 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 + ).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) + self.survey_private.user_input_from_formio(answers, private_user_input.id) + self.assertEqual(self.survey_private.answer_count, 1)