summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorAurélien Warnon <awa@odoo.com>2020-03-27 10:40:09 +0000
committerAurélien Warnon <awa@odoo.com>2020-03-31 10:39:05 +0000
commit7b79c3244d0b52eb74f5c87da903c98f1175040b (patch)
tree9c640b63df2d991fe3a54d892616198f15f350d4 /addons
parent278fb50dbcac7f4295d13676194360050850e29a (diff)
[IMP] website_event_questions: allow 'free text' questions
PURPOSE Allow "free text" questions in the event questions asked to attendees, in addition to the existing "selection" type. e.g: "What are your allergies?" SPECS The implementation is heavily based on the modelisation we chose for the survey module since the features become similar in terms of question types and inputs. - Introduce a 'question_type' that is either 'simple_choice' (= previous behavior) or 'text_box' that will contain the attendee text answer. - Rename the event.answer model to event.question.answer to make it more obvious that it contains answer "suggestions" to a question of type 'simple_choice'. - Introduce a 'event.user_input.line' that is an answer to a specific question for a specific registration. It works very similarly to 'survey.user_input.line' but it's not contained in a 'user_input' model, it's directly linked to the registration using a 'user_input_line_ids' o2m field. The frontend view has been adapted accordingly to display either a selection input or a textarea based on the question type. The frontend orm processing now takes in both answer types (id of suggested answer or text input). On the backend side, views have been adapted to display event questions and attendee answers in a more convenient and logical manner. LINKS PR #45497 Upgrade PR odoo/upgrade#799 Task ID 2189529
Diffstat (limited to 'addons')
-rw-r--r--addons/website_event_questions/__manifest__.py2
-rw-r--r--addons/website_event_questions/controllers/main.py37
-rw-r--r--addons/website_event_questions/data/event_question_demo.xml4
-rw-r--r--addons/website_event_questions/models/event_event.py1
-rw-r--r--addons/website_event_questions/models/event_question.py24
-rw-r--r--addons/website_event_questions/models/event_registration.py28
-rw-r--r--addons/website_event_questions/security/ir.model.access.csv6
-rw-r--r--addons/website_event_questions/tests/common.py8
-rw-r--r--addons/website_event_questions/tests/test_event_internals.py29
-rw-r--r--addons/website_event_questions/views/event_templates.xml35
-rw-r--r--addons/website_event_questions/views/event_views.xml79
11 files changed, 172 insertions, 81 deletions
diff --git a/addons/website_event_questions/__manifest__.py b/addons/website_event_questions/__manifest__.py
index 029ad56fe74..d183e1b0244 100644
--- a/addons/website_event_questions/__manifest__.py
+++ b/addons/website_event_questions/__manifest__.py
@@ -4,7 +4,7 @@
'name': 'Questions on Events',
'description': 'Questions on Events',
'category': 'Marketing',
- 'version': '1.0',
+ 'version': '1.1',
'depends': ['website_event'],
'data': [
'views/event_views.xml',
diff --git a/addons/website_event_questions/controllers/main.py b/addons/website_event_questions/controllers/main.py
index a24ccc941f9..fdcc1f66313 100644
--- a/addons/website_event_questions/controllers/main.py
+++ b/addons/website_event_questions/controllers/main.py
@@ -1,28 +1,47 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from odoo.http import request
+
from odoo.addons.website_event.controllers.main import WebsiteEventController
class WebsiteEvent(WebsiteEventController):
def _process_attendees_form(self, event, form_details):
- ''' Process data posted from the attendee details form. '''
+ """ Process data posted from the attendee details form.
+ Extracts question answers:
+ - For both questions asked 'once_per_order' and questions asked to every attendee
+ - For questions of type 'simple_choice', extracting the suggested answer id
+ - For questions of type 'text_box', extracting the text answer of the attendee. """
registrations = super(WebsiteEvent, self)._process_attendees_form(event, form_details)
for registration in registrations:
- registration['answer_ids'] = []
+ registration['registration_answer_ids'] = []
general_answer_ids = []
for key, value in form_details.items():
- if 'answer_ids' in key:
- index = int(key.split('-')[0])
- if not index:
- general_answer_ids.append([4, int(value)])
- else:
- registrations[index-1]['answer_ids'].append([4, int(value)])
+ if 'question_answer' in key and value:
+ dummy, registration_index, question_id = key.split('-')
+ question = request.env['event.question'].browse(int(question_id))
+ answer_values = None
+ if question.question_type == 'simple_choice':
+ answer_values = {
+ 'question_id': int(question_id),
+ 'value_answer_id': int(value)
+ }
+ elif question.question_type == 'text_box':
+ answer_values = {
+ 'question_id': int(question_id),
+ 'value_text_box': value
+ }
+
+ if answer_values and not int(registration_index):
+ general_answer_ids.append((0, 0, answer_values))
+ elif answer_values:
+ registrations[int(registration_index) - 1]['registration_answer_ids'].append((0, 0, answer_values))
for registration in registrations:
- registration['answer_ids'].extend(general_answer_ids)
+ registration['registration_answer_ids'].extend(general_answer_ids)
return registrations
diff --git a/addons/website_event_questions/data/event_question_demo.xml b/addons/website_event_questions/data/event_question_demo.xml
index 414ddb44735..fc45310dd52 100644
--- a/addons/website_event_questions/data/event_question_demo.xml
+++ b/addons/website_event_questions/data/event_question_demo.xml
@@ -6,12 +6,12 @@
<field name="once_per_order" eval="False"/>
<field name="event_type_id" ref="event.event_type_data_conference"/>
</record>
- <record id="event_type_data_conference_question_0_answer_0" model="event.answer">
+ <record id="event_type_data_conference_question_0_answer_0" model="event.question.answer">
<field name="name">Yes</field>
<field name="sequence">1</field>
<field name="question_id" ref="website_event_questions.event_type_data_conference_question_0"/>
</record>
- <record id="event_type_data_conference_question_0_answer_1" model="event.answer">
+ <record id="event_type_data_conference_question_0_answer_1" model="event.question.answer">
<field name="name">No</field>
<field name="sequence">2</field>
<field name="question_id" ref="website_event_questions.event_type_data_conference_question_0"/>
diff --git a/addons/website_event_questions/models/event_event.py b/addons/website_event_questions/models/event_event.py
index 3a7984a30a0..bd2461a75cd 100644
--- a/addons/website_event_questions/models/event_event.py
+++ b/addons/website_event_questions/models/event_event.py
@@ -33,6 +33,7 @@ class EventEvent(models.Model):
event.question_ids = [(5, 0, 0)] + [
(0, 0, {
'title': question.title,
+ 'question_type': question.question_type,
'sequence': question.sequence,
'once_per_order': question.once_per_order,
})
diff --git a/addons/website_event_questions/models/event_question.py b/addons/website_event_questions/models/event_question.py
index 1081f96f6c9..4fbafa62eae 100644
--- a/addons/website_event_questions/models/event_question.py
+++ b/addons/website_event_questions/models/event_question.py
@@ -12,9 +12,12 @@ class EventQuestion(models.Model):
_description = 'Event Question'
title = fields.Char(required=True, translate=True)
+ question_type = fields.Selection([
+ ('simple_choice', 'Selection'),
+ ('text_box', 'Text Input')], default='simple_choice', string="Question Type", required=True)
event_type_id = fields.Many2one('event.type', 'Event Type', ondelete='cascade')
event_id = fields.Many2one('event.event', 'Event', ondelete='cascade')
- answer_ids = fields.One2many('event.answer', 'question_id', "Answers", required=True, copy=True)
+ answer_ids = fields.One2many('event.question.answer', 'question_id', "Answers", copy=True)
sequence = fields.Integer(default=10)
once_per_order = fields.Boolean('Ask only once per order',
help="If True, this question will be asked only once and its value will be propagated to every attendees."
@@ -37,11 +40,24 @@ class EventQuestion(models.Model):
}) for answer in event.event_type_id.question_ids.filtered(lambda question: question.title == vals.get('title')).mapped('answer_ids')]
return super(EventQuestion, self).create(vals)
+ def write(self, vals):
+ """ We add a check to prevent changing the question_type of a question that already has answers.
+ Indeed, it would mess up the event.registration.answer (answer type not matching the question type). """
-class EventAnswer(models.Model):
- _name = 'event.answer'
+ if 'question_type' in vals:
+ questions_new_type = self.filtered(lambda question: question.question_type != vals['question_type'])
+ if questions_new_type:
+ answer_count = self.env['event.registration.answer'].search_count([('question_id', 'in', questions_new_type.ids)])
+ if answer_count > 0:
+ raise UserError(_("You cannot change the question type of a question that already has answers!"))
+ return super(EventQuestion, self).write(vals)
+
+
+class EventQuestionAnswer(models.Model):
+ """ Contains suggested answers to a 'simple_choice' event.question. """
+ _name = 'event.question.answer'
_order = 'sequence,id'
- _description = 'Event Answer'
+ _description = 'Event Question Answer'
name = fields.Char('Answer', required=True, translate=True)
question_id = fields.Many2one('event.question', required=True, ondelete='cascade')
diff --git a/addons/website_event_questions/models/event_registration.py b/addons/website_event_questions/models/event_registration.py
index b8f23da99d7..5bb81b48703 100644
--- a/addons/website_event_questions/models/event_registration.py
+++ b/addons/website_event_questions/models/event_registration.py
@@ -4,22 +4,24 @@
from odoo import fields, models
-class EventRegistrationAnswer(models.Model):
- ''' This m2m table has to be explicitly instanciated as we need unique ids
- in the reporting view event.question.report.
+class EventRegistration(models.Model):
+ """ Store answers on attendees. """
+ _inherit = 'event.registration'
- This model is purely technical. '''
+ registration_answer_ids = fields.One2many('event.registration.answer', 'registration_id', string='Attendee Answers')
+class EventRegistrationAnswer(models.Model):
+ """ Represents the user input answer for a single event.question """
_name = 'event.registration.answer'
- _table = 'event_registration_answer'
_description = 'Event Registration Answer'
- event_answer_id = fields.Many2one('event.answer', required=True, ondelete='cascade')
- event_registration_id = fields.Many2one('event.registration', required=True, ondelete='cascade')
-
-
-class EventRegistration(models.Model):
- """ Store answers on attendees. """
- _inherit = 'event.registration'
+ question_id = fields.Many2one('event.question', required=True, ondelete='cascade')
+ registration_id = fields.Many2one('event.registration', required=True, ondelete='cascade')
+ event_id = fields.Many2one('event.event', related='registration_id.event_id')
+ question_type = fields.Selection(related='question_id.question_type')
+ value_answer_id = fields.Many2one('event.question.answer', string="Suggested answer")
+ value_text_box = fields.Text('Text answer')
- answer_ids = fields.Many2many('event.answer', 'event_registration_answer', string='Answers')
+ _sql_constraints = [
+ ('value_check', "CHECK(value_answer_id IS NOT NULL OR COALESCE(value_text_box, '') <> '')", "There must be a suggested value or a text value.")
+ ]
diff --git a/addons/website_event_questions/security/ir.model.access.csv b/addons/website_event_questions/security/ir.model.access.csv
index 66d25208471..ea509ea3f85 100644
--- a/addons/website_event_questions/security/ir.model.access.csv
+++ b/addons/website_event_questions/security/ir.model.access.csv
@@ -1,6 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
event_question_all,event.question.all,model_event_question,,1,0,0,0
event_question_event_user,event.question.event.user,model_event_question,event.group_event_user,1,1,1,1
-event_answer_all,event.answer.all,model_event_answer,,1,0,0,0
-event_answer_event_user,event.answer.event.user,model_event_answer,event.group_event_user,1,1,1,1
-event_registration_answer_all,access_event_registration_answer,model_event_registration_answer,,1,1,1,1
+event_question_answer_all,event.question.answer.all,model_event_question_answer,,1,0,0,0
+event_question_answer_event_user,event.question.answer.event.user,model_event_question_answer,event.group_event_user,1,1,1,1
+event_registration_answer_event_user,event.registration.answer.event.user,model_event_registration_answer,event.group_event_user,1,1,1,1
diff --git a/addons/website_event_questions/tests/common.py b/addons/website_event_questions/tests/common.py
index 431eb1c376d..7ad2697a81f 100644
--- a/addons/website_event_questions/tests/common.py
+++ b/addons/website_event_questions/tests/common.py
@@ -12,6 +12,7 @@ class TestEventQuestionCommon(TestEventCommon):
cls.event_question_1 = cls.env['event.question'].create({
'title': 'Question1',
+ 'question_type': 'simple_choice',
'event_type_id': cls.event_type_complex.id,
'once_per_order': False,
'answer_ids': [
@@ -21,6 +22,7 @@ class TestEventQuestionCommon(TestEventCommon):
})
cls.event_question_2 = cls.env['event.question'].create({
'title': 'Question2',
+ 'question_type': 'simple_choice',
'event_type_id': cls.event_type_complex.id,
'once_per_order': True,
'answer_ids': [
@@ -28,4 +30,10 @@ class TestEventQuestionCommon(TestEventCommon):
(0, 0, {'name': 'Q2-Answer2'})
],
})
+ cls.event_question_3 = cls.env['event.question'].create({
+ 'title': 'Question3',
+ 'question_type': 'text_box',
+ 'event_type_id': cls.event_type_complex.id,
+ 'once_per_order': True,
+ })
cls.event_type_complex.write({'use_questions': True})
diff --git a/addons/website_event_questions/tests/test_event_internals.py b/addons/website_event_questions/tests/test_event_internals.py
index d3494cae49c..c2cfe1f40d1 100644
--- a/addons/website_event_questions/tests/test_event_internals.py
+++ b/addons/website_event_questions/tests/test_event_internals.py
@@ -23,13 +23,18 @@ class TestEventData(TestEventQuestionCommon):
'date_end': FieldsDatetime.to_string(datetime.today() + timedelta(days=15)),
})
+ self.assertEqual(
+ event.question_ids.mapped('question_type'),
+ ['simple_choice', 'simple_choice', 'text_box'])
self.assertEqual(event.specific_question_ids.title, 'Question1')
self.assertEqual(
set(event.specific_question_ids.mapped('answer_ids.name')),
set(['Q1-Answer1', 'Q1-Answer2']))
- self.assertEqual(event.general_question_ids.title, 'Question2')
+ self.assertEqual(len(event.general_question_ids), 2)
+ self.assertEqual(event.general_question_ids[0].title, 'Question2')
+ self.assertEqual(event.general_question_ids[1].title, 'Question3')
self.assertEqual(
- set(event.general_question_ids.mapped('answer_ids.name')),
+ set(event.general_question_ids[0].mapped('answer_ids.name')),
set(['Q2-Answer1', 'Q2-Answer2']))
def test_process_attendees_form(self):
@@ -45,20 +50,28 @@ class TestEventData(TestEventQuestionCommon):
'1-email': 'pixis@gmail.com',
'1-phone': '+32444444444',
'1-event_ticket_id': '2',
- '1-answer_ids-8': '5',
'2-name': 'Geluchat',
'2-email': 'geluchat@gmail.com',
'2-phone': '+32777777777',
'2-event_ticket_id': '3',
- '2-answer_ids-8': '9',
- '0-answer_ids-3': '7',
- '0-answer_ids-4': '1',
+ 'question_answer-1-%s' % self.event_question_1.id: '5',
+ 'question_answer-2-%s' % self.event_question_1.id: '9',
+ 'question_answer-0-%s' % self.event_question_2.id: '7',
+ 'question_answer-0-%s' % self.event_question_3.id: 'Free Text',
}
with MockRequest(self.env):
registrations = WebsiteEvent()._process_attendees_form(event, form_details)
self.assertEqual(registrations, [
- {'name': 'Pixis', 'email': 'pixis@gmail.com', 'phone': '+32444444444', 'event_ticket_id': 2, 'answer_ids': [[4, 5], [4, 7], [4, 1]]},
- {'name': 'Geluchat', 'email': 'geluchat@gmail.com', 'phone': '+32777777777', 'event_ticket_id': 3, 'answer_ids': [[4, 9], [4, 7], [4, 1]]}
+ {'name': 'Pixis', 'email': 'pixis@gmail.com', 'phone': '+32444444444', 'event_ticket_id': 2,
+ 'registration_answer_ids': [
+ (0, 0, {'question_id': self.event_question_1.id, 'value_answer_id': 5}),
+ (0, 0, {'question_id': self.event_question_2.id, 'value_answer_id': 7}),
+ (0, 0, {'question_id': self.event_question_3.id, 'value_text_box': 'Free Text'})]},
+ {'name': 'Geluchat', 'email': 'geluchat@gmail.com', 'phone': '+32777777777', 'event_ticket_id': 3,
+ 'registration_answer_ids': [
+ (0, 0, {'question_id': self.event_question_1.id, 'value_answer_id': 9}),
+ (0, 0, {'question_id': self.event_question_2.id, 'value_answer_id': 7}),
+ (0, 0, {'question_id': self.event_question_3.id, 'value_text_box': 'Free Text'})]}
])
diff --git a/addons/website_event_questions/views/event_templates.xml b/addons/website_event_questions/views/event_templates.xml
index 5b63aaa9321..ef6d1a4c791 100644
--- a/addons/website_event_questions/views/event_templates.xml
+++ b/addons/website_event_questions/views/event_templates.xml
@@ -7,12 +7,9 @@
<t t-if="event.specific_question_ids">
<div t-foreach="event.specific_question_ids" t-as="question" class="row">
<div class="col-lg-6 mt-2">
- <label t-esc="question.title"/>
- <select t-attf-name="#{counter}-answer_ids-#{question.id}" class="custom-select" required="true">
- <t t-foreach="question.answer_ids" t-as="answer">
- <option t-esc="answer.name" t-att-value="answer.id"/>
- </t>
- </select>
+ <t t-call="website_event_questions.registration_event_question">
+ <t t-set="registration_index" t-value="counter"/>
+ </t>
</div>
</div>
</t>
@@ -21,14 +18,12 @@
<xpath expr="//*[hasclass('modal-footer')]" position="before">
<t t-if="event.general_question_ids">
<div class="modal-body bg-light border-bottom">
+ <h5>General questions</h5>
<div t-foreach="event.general_question_ids" t-as="question" class="row">
- <div class="col-lg-6">
- <label class="h5" t-esc="question.title"/>
- <select t-attf-name="0-answer_ids-#{question.id}" class="custom-select" required="true">
- <t t-foreach="question.answer_ids" t-as="answer">
- <option t-esc="answer.name" t-att-value="answer.id"/>
- </t>
- </select>
+ <div class="col-lg-6 mt-2">
+ <t t-call="website_event_questions.registration_event_question">
+ <t t-set="registration_index" t-value="0"/>
+ </t>
</div>
</div>
</div>
@@ -36,4 +31,18 @@
</xpath>
</template>
+<template id="registration_event_question" name="Registration Event Question">
+ <label t-esc="question.title"/>
+ <t t-if="question.question_type == 'simple_choice'">
+ <select t-attf-name="question_answer-#{registration_index}-#{question.id}" class="custom-select" required="true">
+ <t t-foreach="question.answer_ids" t-as="answer">
+ <option t-esc="answer.name" t-att-value="answer.id"/>
+ </t>
+ </select>
+ </t>
+ <t t-elif="question.question_type == 'text_box'">
+ <textarea t-attf-name="question_answer-#{registration_index}-#{question.id}" class="col-lg-12 form-control"/>
+ </t>
+</template>
+
</odoo>
diff --git a/addons/website_event_questions/views/event_views.xml b/addons/website_event_questions/views/event_views.xml
index 2f209e5c46b..4f854871cdd 100644
--- a/addons/website_event_questions/views/event_views.xml
+++ b/addons/website_event_questions/views/event_views.xml
@@ -6,24 +6,37 @@
<field name="model">event.question</field>
<field name="arch" type="xml">
<form string="Question">
- <h1><field name="title" /></h1>
- <field name="once_per_order"/>
- <label for="once_per_order"/>
- <field name="answer_ids">
- <tree editable="bottom">
- <field name="sequence" widget="handle" />
- <field name="name"/>
- </tree>
- </field>
+ <sheet>
+ <h1><field name="title" /></h1>
+ <group>
+ <group>
+ <div colspan="2">
+ <field name="once_per_order"/>
+ <label for="once_per_order"/>
+ </div>
+ <field name="question_type" widget="radio" options="{'horizontal': true}" />
+ </group>
+ </group>
+ <notebook attrs="{'invisible': [('question_type', '!=', 'simple_choice')]}">
+ <page string="Answers" name="answers">
+ <field name="answer_ids">
+ <tree editable="bottom">
+ <field name="sequence" widget="handle" />
+ <field name="name"/>
+ </tree>
+ </field>
+ </page>
+ </notebook>
+ </sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_event_answer_simplified_form">
- <field name="name">event.answer.simplified.form</field>
- <field name="model">event.answer</field>
+ <field name="name">event.question.answer.simplified.form</field>
+ <field name="model">event.question.answer</field>
<field name="arch" type="xml">
- <form string="Question">
+ <form string="Answer">
<group>
<field name="name"/>
</group>
@@ -45,7 +58,12 @@
<label for="use_questions"/>
<div class="row mt16" attrs="{'invisible': [('use_questions', '=', False)]}">
<div class="col-lg-9">
- <field name="question_ids"/>
+ <field name="question_ids">
+ <tree>
+ <field name="title"/>
+ <field name="question_type" />
+ </tree>
+ </field>
</div>
</div>
</div>
@@ -67,7 +85,9 @@
<field name="sequence" widget="handle" />
<field name="title"/>
<field name="once_per_order"/>
- <field name="answer_ids"/>
+ <field name="question_type" />
+ <field name="answer_ids" widget="many2many_tags"
+ attrs="{'invisible': [('question_type', '!=', 'simple_choice')]}" />
</tree>
</field>
</page>
@@ -81,20 +101,23 @@
<field name="model">event.registration</field>
<field name="inherit_id" ref="event.view_event_registration_form" />
<field name="arch" type="xml">
- <group name="event" position="inside">
- <field name="answer_ids" widget="many2many_tags" domain="[('question_id.event_id', '=', event_id)]" options="{'no_create_edit': True}"/>
- </group>
- </field>
-</record>
-
-<record model="ir.ui.view" id="view_registration_search_inherit_question">
- <field name="name">event.registration.search.inherit.question</field>
- <field name="model">event.registration</field>
- <field name="inherit_id" ref="event.view_registration_search"/>
- <field name="arch" type="xml">
- <search position="inside">
- <field name="answer_ids" string="Answers"/>
- </search>
+ <sheet position="inside">
+ <notebook>
+ <page string="Questions" name="questions">
+ <field name="registration_answer_ids" widget="one2many">
+ <tree editable="bottom">
+ <field name="event_id" invisible="1" />
+ <field name="question_id" domain="[('event_id', '=', event_id)]" options="{'no_create': True}" />
+ <field name="question_type" />
+ <field name="value_answer_id"
+ attrs="{'invisible': [('question_type', '!=', 'simple_choice')]}"
+ domain="[('question_id', '=', question_id)]" options="{'no_create': True}"/>
+ <field name="value_text_box" attrs="{'invisible': [('question_type', '!=', 'text_box')]}" />
+ </tree>
+ </field>
+ </page>
+ </notebook>
+ </sheet>
</field>
</record>