Part 4: The Post-Experimental Questionnaire ============================================================= After participants ended their stay in the fictional country of Novaland, they were asked to complete a post-experimental questionnaire. This questionnaire included a variety of questions on participants' socio-demographic characteristics, their real-life attitudes, and their participation in the experiment. Additionally, participants were asked if they wanted to receive a digital certificate of their stay as a citizen of Novaland. Pages of the Post-Experimental Questionnaire ------------------------------------------------------------------------- The post-experimental questionnaire consists of only three pages: 1. **PostSurvey**: This page contains the entire questionnaire. Here, the questions were dynamically rendered using a SurveyJS survey model. 2. **ReceiveCert**: On this page, participants could download a digital certificate of their stay in Novaland. The certificate was generated as a PDF file. 3. **LastPage**: On this last page, we thanked the participants for their participation and provided them with a link to be redirected to the sample provider website. The PostSurvey Page ^^^^^^^^^^^^^^^^^^^^^^^^^^ The PostSurvey page is the main page of the post-experimental questionnaire. It contains a dynamically rendered survey model that includes various questions about the participants' socio-demographic characteristics, their attitudes, and their experience in the experiment. The content of the survey is defined in a separate JavaScript file, which is loaded into the page. In the __init__.py file of the :code:`post` app, we define the **PostSurvey** class, which mainly consists of four methods: 1. The :code:`live_method` method is used to handle the real-time data submission from the survey. This method is required as the survey is rendered dynamically and the data is sent to the server in real-time. If we used the standard procedure of submitting the survey data only after participants answered all questions on this page, the survey would not be able to handle the real-time data submission but rather save the data only after participants answered all questions on this page. Thus, using the :code:`live_method` method is a more efficient and safer way to handle the survey data submission. .. dropdown:: PostSurvey live_method :icon: terminal .. code-block:: python @staticmethod def live_method(player, data): # let's do this: store the data also in participant vars as a dict. # before assying (process_survey_data) let's check if all keys are there. if not all, let's set missing keys in # players' level to '' (empty string) silently (via try/except) print(f'data received: {data}') old_data = player.participant.vars.get('post_survey', {}) existing_keys = old_data.keys() new_keys = data.keys() copy_data = data.copy() print(data.keys()) print(old_data.keys()) print('***') for k in old_data.keys(): if k not in new_keys: copy_data[k] = '' old_data.update(copy_data) player.participant.vars['post_survey'] = copy_data PostSurvey.process_survey_data(player, copy_data) 2. The :code:`process_survey_data` method processes the survey data collected by the :code:`live_method`. It is called at the end of the :code:`live_method` method to convert the data into the appropriate format and assign it to the corresponding fields in the Player model. It ensures that the data is correctly stored in the database. For this, it checks the type of each field in the Player model and converts the data accordingly. The values are then assigned to the Player model instance, e.g., with :code:`converted_value = int(value)` for integer fields, :code:`converted_value = str(value)` for string fields, and so on. This method is crucial for ensuring that the data is correctly stored. Additionally, there is a logging mechanism in place to log any errors that occur during the data processing. This helps in debugging and ensuring data integrity. .. dropdown:: PostSurvey process_survey_data :icon: terminal .. code-block:: python @staticmethod def process_survey_data(player, survey_results): # Get the SQLAlchemy mapper for the Player model mapper = inspect(player.__class__) # Iterate over each key-value pair in the survey results for key, value in survey_results.items(): logger.info(f'Processing {key}: {value}') try: # Check if the column exists in the model if key not in mapper.columns: logger.warning(f'No such field: {key}') continue # Get the column object column = mapper.columns[key] column_type = column.type # Determine the column type and convert the value accordingly if isinstance(column_type, (Integer, BigInteger, SmallInteger)): # Handle integer fields if isinstance(value, int): converted_value = value elif isinstance(value, str) and value.isdigit(): converted_value = int(value) else: # Attempt to convert to integer converted_value = int(value) elif isinstance(column_type, (String, Text)): # For string/text fields, ensure the value is a string converted_value = str(value) elif isinstance(column_type, Boolean): # Convert to boolean if isinstance(value, bool): converted_value = value elif isinstance(value, str): converted_value = value.lower() in ['true', '1', 'yes'] else: converted_value = bool(value) elif isinstance(column_type, (Float, Numeric)): # Handle float and numeric fields converted_value = float(value) else: # For other types, assign as-is or handle accordingly converted_value = value # Assign the converted value to the model instance setattr(player, key, converted_value) logger.info(f'Successfully set {key} to {converted_value}') except ValueError as ve: logger.error(f'Value error for field "{key}": {value} - {ve}') except Exception as e: logger.error(f'Error setting field "{key}": {e}') 3. The :code:`get_form_fields` method returns a list of form fields that are required for the survey. This method is used to dynamically generate the form fields based on the survey definition. It is only used if the participant is not a browser bot (i.e., if the participant is a real user and not a bot). The method returns a list of field names that are required for the survey. This allows for flexibility in the survey design, as the fields can be dynamically generated based on the survey definition. .. dropdown:: PostSurvey get_form_fields :icon: terminal .. code-block:: python def get_form_fields(player): if player.participant.is_browser_bot: r = ['nova_certificate_bin', 'nova_certificate_open', 'nova_dec', 'bribery_exp_pers', 'bribery_exp', 'bribery_exp_smone', 'att_state_inequality', 'soc_trust', 'helpful', 'att_taxes_welfare', 'att_incr_welfare', 'att_immigration', 'att_voting_intention', 'att_voting_intention_open', 'soc_gender', 'soc_birthyear', 'soc_marital_status', 'soc_hhsize', 'soc_children', 'soc_employment', 'soc_employment_open', 'soc_pers_income', 'soc_education', 'soc_job_education', 'att_leftright', 'soc_postalcode', 'soc_citizenship', 'soc_born_germany', 'soc_parents_born_germany', 'svx_participation_location', 'svx_participation_device', 'svx_interest', 'svx_difficulty', 'svx_privacy', 'svx_technical_problems_bin', 'svx_technical_problems_open', 'svx_gaming', 'svx_purpose', 'svx_final_comments'] return r 4. The :code:`post` method is called when the survey is submitted. It processes the survey data and calls the :code:`process_survey_data` method to handle the data submission. This method is responsible for handling the survey submission and ensuring that the data is correctly processed and stored in the database. It also handles any errors that may occur during the submission process, such as invalid JSON data or unexpected errors. .. dropdown:: PostSurvey post :icon: terminal .. code-block:: python def post(self): if self.participant.is_browser_bot: return super().post() # Assuming self._form_data contains 'surveyResults' try: # Parse the JSON data from the survey survey_results = json.loads(self._form_data.get('surveyResults')) pprint(survey_results) # For debugging purposes PostSurvey.process_survey_data(self.player, survey_results) except json.JSONDecodeError as e: logger.error(f'Invalid JSON data: {e}') except Exception as e: logger.error(f'Unexpected error: {e}') # Proceed with the superclass's post method return super().post() 5. As always in this oTree project, the :code:`form_model` attribute is set to :code:`player`, indicating that the form fields are associated with the Player model. Now that the PostSurvey page is defined, we will have a look at the **HTML template** that is used to render the survey. The template includes the necessary JavaScript and CSS files to render the survey dynamically. It also includes a hidden input field to store the survey results, which will be submitted when the survey is completed. On the HTML template, the PostSurvey page uses the SurveyJS library to render the survey dynamically. The survey definition is loaded from a separate JavaScript file, which contains the survey questions and their properties. The template also includes a progress bar that shows the progress of the survey as participants answer the questions. The progress bar is updated in real-time as participants navigate through the survey pages. The template is more complex than the previous pages, as it includes JavaScript code to handle the survey rendering, data submission, and progress bar updates. The JavaScript code is responsible for initializing the SurveyJS model, loading the survey data from the local storage of participants' browsers, and handling the survey completion event. Overall, the template handles the rendering of the data. The content of the survey is defined in the :code:`survey_definition.js` file, which is loaded into the template. The survey definition includes the questions, their types, and other properties. Thus, it is not necessary to understand the details of HTML and Javascript code in order to understand how the PostSurvey page works. Thus, the code following code from the :code:` PostSurvey` template will not be explained in detail. We annotated the functionality of the most important parts of the code below. .. dropdown:: PostSurvey HTML Template :icon: terminal .. code-block:: html // The first section loads the necessary JavaScript libraries and stylesheets for SurveyJS // The second section defines the SurveyJS model and handles the survey rendering and data submission