[ADD] website_event_questions: ask questions to event attendees

[IMP] event,event_sale,website_event_sale: refactoring to be more easily extended

(code portions are courtesy of Thibault Delavallée <tde@odoo.com>)
This commit is contained in:
Richard Mathot
2015-02-19 11:22:59 +01:00
parent 2334917ed3
commit 194d95cd47
21 changed files with 508 additions and 44 deletions
+18
View File
@@ -303,6 +303,24 @@ class event_registration(models.Model):
registration.sudo().confirm_registration()
return registration
@api.model
def _prepare_attendee_values(self, registration):
""" Method preparing the values to create new attendees based on a
sale order line. It takes some registration data (dict-based) that are
optional values coming from an external input like a web page. This method
is meant to be inherited in various addons that sell events. """
partner_id = registration.pop('partner_id', self.env.user.partner_id)
event_id = registration.pop('event_id', False)
data = {
'name': registration.get('name', partner_id.name),
'phone': registration.get('phone', partner_id.phone),
'email': registration.get('email', partner_id.email),
'partner_id': partner_id.id,
'event_id': event_id and event_id.id or False,
}
data.update({key: registration[key] for key in registration.keys() if key in self._fields})
return data
@api.one
def do_draft(self):
self.state = 'draft'
+9 -1
View File
@@ -227,7 +227,15 @@
<field name="event_id" ref="event_1"/>
</record>
<record id="reg_0_2" model="event.registration">
<record id="reg_1_3" model="event.registration">
<field name="name">Delta PC</field>
<field name="email">deltapc@yourcompany.example.com</field>
<field name="phone">+1 510 340 2385</field>
<field name="partner_id" ref="base.res_partner_4"/>
<field name="event_id" ref="event_1"/>
</record>
<record id="reg_2_0" model="event.registration">
<field name="name">Camptocamp</field>
<field name="email">openerp@camptocamp.com</field>
<field name="phone">+41 21 619 10 04 </field>
+1 -1
View File
@@ -305,7 +305,7 @@
</header>
<sheet string="Registration">
<group>
<group string="Attendee Information" name="attendee">
<group string="Attendee" name="attendee">
<field name="name"/>
<field name="phone"/>
<field name="email"/>
+18
View File
@@ -175,3 +175,21 @@ class event_registration(models.Model):
'order': res.origin or res.sale_order_id.name})
res.message_post(body=message)
return res
@api.model
def _prepare_attendee_values(self, registration):
""" Override to add sale related stuff """
line_id = registration.get('sale_order_line_id')
if line_id:
registration.setdefault('partner_id', line_id.order_id.partner_id)
att_data = super(event_registration, self)._prepare_attendee_values(registration)
if line_id:
att_data.update({
'event_id': line_id.event_id.id,
'event_id': line_id.event_id.id,
'event_ticket_id': line_id.event_ticket_id.id,
'origin': line_id.order_id.name,
'sale_order_id': line_id.order_id.id,
'sale_order_line_id': line_id.id,
})
return att_data
+5 -12
View File
@@ -62,7 +62,8 @@ class sale_order_line(osv.osv):
order line has a product_uom_qty attribute that will be the number of
registrations linked to this line. This method update existing registrations
and create new one for missing one. """
registrations = self.env['event.registration'].search([('sale_order_line_id', 'in', self.ids)])
Registration = self.env['event.registration']
registrations = Registration.search([('sale_order_line_id', 'in', self.ids)])
for so_line in [l for l in self if l.event_id]:
existing_registrations = registrations.filtered(lambda self: self.sale_order_line_id.id == so_line.id)
if confirm:
@@ -75,17 +76,9 @@ class sale_order_line(osv.osv):
if registration_data:
registration = registration_data.pop()
# TDE CHECK: auto confirmation
self.env['event.registration'].with_context(registration_force_draft=True).create({
'name': registration.get('name', so_line.order_id.partner_id.name),
'phone': registration.get('phone', so_line.order_id.partner_id.phone),
'email': registration.get('email', so_line.order_id.partner_id.email),
'event_id': so_line.event_id.id,
'event_ticket_id': so_line.event_ticket_id.id,
'partner_id': so_line.order_id.partner_id.id,
'origin': so_line.order_id.name,
'sale_order_id': so_line.order_id.id,
'sale_order_line_id': so_line.id,
})
registration['sale_order_line_id'] = so_line
self.env['event.registration'].with_context(registration_force_draft=True).create(
Registration._prepare_attendee_values(registration))
return True
def button_confirm(self, cr, uid, ids, context=None):
+1 -1
View File
@@ -35,8 +35,8 @@
</field>
<group name="event" position="after">
<group string="Origin">
<field name="origin" attrs="{'invisible': [('sale_order_id', '!=', False)]}"/>
<field name="sale_order_id"/>
<field name="origin" attrs="{'invisible': [('sale_order_id', '!=', False)]}"/>
<field name="sale_order_line_id" readonly="1" attrs="{'invisible': [('sale_order_id', '=', False)]}"/>
</group>
</group>
+13 -10
View File
@@ -4,7 +4,6 @@ import time
import werkzeug.urls
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from collections import OrderedDict
from openerp import http
from openerp import tools, SUPERUSER_ID
@@ -244,9 +243,16 @@ class website_event(http.Controller):
def _process_registration_details(self, details):
''' Process data posted from the attendee details form. '''
registrations = {}
global_values = {}
for key, value in details.iteritems():
counter, field_name = key.split('-', 1)
registrations.setdefault(counter, dict())[field_name] = value
if counter == '0':
global_values[field_name] = value
else:
registrations.setdefault(counter, dict())[field_name] = value
for key, value in global_values.iteritems():
for registration in registrations.values():
registration[key] = value
return registrations.values()
@http.route(['/event/<model("event.event"):event>/registration/confirm'], type='http', auth="public", methods=['POST'], website=True)
@@ -256,16 +262,13 @@ class website_event(http.Controller):
registrations = self._process_registration_details(post)
registration_ids = []
user = request.registry.get('res.users').browse(cr, uid, uid, context=context)
for registration in registrations:
registration['event_id'] = event
registration_ids.append(
Registration.create(cr, SUPERUSER_ID, {
'name': registration.get('name', user.name),
'phone': registration.get('phone', user.phone),
'email': registration.get('email', user.email),
'partner_id': user.partner_id.id,
'event_id': event.id,
}, context=context))
Registration.create(
cr, SUPERUSER_ID,
Registration._prepare_attendee_values(cr, SUPERUSER_ID, registration),
context=context))
attendees = Registration.browse(cr, uid, registration_ids, context=context)
return request.website.render("website_event.registration_complete", {
+16 -19
View File
@@ -428,31 +428,28 @@
<t t-set="counter_type" t-value="1"/>
<t t-set="counter" t-value="0"/>
<t t-foreach="tickets" t-as="ticket">
<div>
<h4>
<strong>
Ticket Type #<t t-raw="counter_type"/>: <t t-esc="ticket['name']"/> (
<t t-if="ticket['price'] > 0">
<t t-esc="ticket['price']"/>
<t t-esc="website.pricelist_id.currency_id.symbol"/>
</t>
<t t-if="ticket['price'] == 0">Free</t>)
</strong>
</h4><hr/>
</div>
<h4 class="page-header mt16">
<strong>
Ticket Type #<t t-raw="counter_type"/>: <t t-esc="ticket['name']"/> (
<t t-if="ticket['price'] > 0">
<t t-esc="ticket['price']"/>
<t t-esc="website.pricelist_id.currency_id.symbol"/>
</t>
<t t-if="ticket['price'] == 0">Free</t>)
</strong>
</h4>
<div class="row">
<div class="col-md-4"><strong>Name</strong></div>
<div class="col-md-3"><strong>Phone</strong> <span class="text-muted">(Optional)</span></div>
<div class="col-md-5"><strong>Email</strong> <span class="text-muted">(Optional)</span></div>
</div>
<t t-foreach="range(1, ticket['quantity'] + 1)" t-as="att_counter">
<t t-foreach="range(1, ticket['quantity'] + 1)" t-as="att_counter" name="attendee_loop">
<t t-set="counter" t-value="counter + 1"/>
<div class="row">
<div class="col-md-4"><input class='form-control input-sm' type='text' t-attf-name="#{counter}-name" required="This field is required" t-att-placeholder="'Attendee #%s' %counter"/></div>
<div class="col-md-3"><input class='form-control input-sm' type='tel' t-attf-name="#{counter}-phone"/></div>
<div class="col-md-5"><input class='form-control input-sm' type='email' t-attf-name="#{counter}-email"/></div>
<div class="row mb4">
<div class="col-md-4"><input class='form-control' type='text' t-attf-name="#{counter}-name" required="This field is required" t-att-placeholder="'Attendee #%s' %counter"/></div>
<div class="col-md-3"><input class='form-control' type='tel' t-attf-name="#{counter}-phone"/></div>
<div class="col-md-5"><input class='form-control' type='email' t-attf-name="#{counter}-email"/></div>
<input class='hidden' type='text' t-attf-name="#{counter}-ticket_id" t-attf-value="#{ticket['id']}"/>
<hr style="width:50%"/>
</div>
</t>
<t t-set="counter_type" t-value="counter_type + 1"/>
@@ -461,7 +458,7 @@
</div>
<div class="modal-footer">
<div class="pull-left">
<button type="submit" class="btn btn-primary">Continue</button> or
<button type="submit" class="btn btn-primary">Continue</button> or
<a class="js_goto_event"> Cancel Registration</a>
</div>
</div>
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2015-TODAY Odoo S.A. (<https://www.odoo.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import controllers
import models
import report
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
{
'name': 'Questions on Events',
'description': 'Questions on Events',
'category': 'Website',
'version': '1.0',
'author': 'Odoo S.A.',
'depends': ['website_event'],
'data': [
'views/website_event_questions_backend.xml',
'views/website_event_questions_templates.xml',
'report/report_event_question_view.xml',
'security/ir.model.access.csv',
],
'demo': [
'data/demo.xml',
],
'installable': True,
}
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
import main
@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
from openerp.addons.website_event.controllers.main import website_event
class website_event(website_event):
def _process_registration_details(self, details):
''' Process data posted from the attendee details form. '''
registrations = super(website_event, self)._process_registration_details(details)
for registration in registrations:
answer_ids = []
for key, value in registration.iteritems():
if key.startswith('answer_ids-'):
answer_ids.append([4, int(value)])
registration['answer_ids'] = answer_ids
return registrations
@@ -0,0 +1,71 @@
<?xml version="1.0"?>
<openerp>
<data>
<record id="event_question_0" model="event.question">
<field name="title">Kind of partner</field>
<field name="event_id" ref="event.event_1"/>
<field name="sequence">1</field>
</record>
<record id="event_question_1" model="event.question">
<field name="title">Veggie</field>
<field name="event_id" ref="event.event_1"/>
<field name="sequence">2</field>
<field name="is_individual" eval="True"/>
</record>
<record id="event_question_2" model="event.question">
<field name="title">Accommodation</field>
<field name="event_id" ref="event.event_1"/>
<field name="sequence">3</field>
</record>
<record id="event_suggestion_0_0" model="event.answer">
<field name="name">Gold</field>
<field name="question_id" ref="event_question_0"/>
<field name="sequence">3</field>
</record>
<record id="event_suggestion_0_1" model="event.answer">
<field name="name">Silver</field>
<field name="question_id" ref="event_question_0"/>
<field name="sequence">2</field>
</record>
<record id="event_suggestion_0_2" model="event.answer">
<field name="name">Bronze</field>
<field name="question_id" ref="event_question_0"/>
<field name="sequence">1</field>
</record>
<record id="event_suggestion_1_0" model="event.answer">
<field name="name">Yes</field>
<field name="question_id" ref="event_question_1"/>
<field name="sequence">2</field>
</record>
<record id="event_suggestion_1_1" model="event.answer">
<field name="name">No</field>
<field name="question_id" ref="event_question_1"/>
<field name="sequence">1</field>
</record>
<record id="event_suggestion_2_0" model="event.answer">
<field name="name">Personal</field>
<field name="question_id" ref="event_question_2"/>
<field name="sequence">1</field>
</record>
<record id="event_suggestion_2_1" model="event.answer">
<field name="name">In Charge</field>
<field name="question_id" ref="event_question_2"/>
<field name="sequence">2</field>
</record>
<record id="event.reg_1_1" model="event.registration">
<field name="answer_ids" eval="[(4, ref('event_suggestion_0_0')), (4, ref('event_suggestion_1_1')), (4, ref('event_suggestion_2_0'))]"/>
</record>
<record id="event.reg_1_2" model="event.registration">
<field name="answer_ids" eval="[(4, ref('event_suggestion_0_2')), (4, ref('event_suggestion_1_1')), (4, ref('event_suggestion_2_0'))]"/>
</record>
<record id="event.reg_1_3" model="event.registration">
<field name="answer_ids" eval="[(4, ref('event_suggestion_0_0')), (4, ref('event_suggestion_1_0')), (4, ref('event_suggestion_2_1'))]"/>
</record>
</data>
</openerp>
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
import event
@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
from openerp import models, fields
class EventEvent(models.Model):
""" Override Event model to add optional questions when buying tickets. """
_inherit = 'event.event'
question_ids = fields.One2many('event.question', 'event_id', 'Questions')
general_question_ids = fields.One2many('event.question', 'event_id', 'Questions',
domain=[('is_individual', '=', False)])
specific_question_ids = fields.One2many('event.question', 'event_id', 'Questions',
domain=[('is_individual', '=', True)])
class EventRegistrationAnswer(models.Model):
''' This m2m table has to be explicitly instanciated as we need unique ids
in the reporting view event.question.report.
This model is purely technical. '''
_name = 'event.registration.answer'
_table = 'event_registration_answer'
event_answer_id = fields.Many2one('event.answer', required=True)
event_registration_id = fields.Many2one('event.registration', required=True)
class EventRegistration(models.Model):
""" Store answers on attendees. """
_inherit = 'event.registration'
answer_ids = fields.Many2many('event.answer', 'event_registration_answer', string='Answers')
class EventQuestion(models.Model):
_name = 'event.question'
_rec_name = 'title'
_order = 'sequence,id'
title = fields.Char(required=True, translate=True)
event_id = fields.Many2one('event.event', required=True, ondelete='cascade')
answer_ids = fields.One2many('event.answer', 'question_id', "Answers", required=True)
sequence = fields.Integer(default=10)
is_individual = fields.Boolean('Ask each attendee',
help="If True, this question will be asked for every attendee of a reservation. If "
"not it will be asked only once and its value propagated to every attendees.")
class EventAnswer(models.Model):
_name = 'event.answer'
_order = 'sequence,id'
name = fields.Char('Answer', required=True, translate=True)
question_id = fields.Many2one('event.question', required=True, ondelete='cascade')
sequence = fields.Integer(default=10)
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
import report_event_registrations_questions
@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Graph view for question analysis-->
<record model="ir.ui.view" id="view_event_question_report_graph">
<field name="name">event.question.report.graph</field>
<field name="model">event.question.report</field>
<field name="arch" type="xml">
<graph string="Questions Analysis">
<field name="answer_id"/>
<field name="question_id"/>
</graph>
</field>
</record>
<!-- Pivot view for question analysis -->
<record model="ir.ui.view" id="view_event_question_report_pivot">
<field name="name">event.question.report.pivot</field>
<field name="model">event.question.report</field>
<field name="arch" type="xml">
<pivot string="Questions Analysis" disable_linking="True">
<field name="event_id" type="row"/>
<field name="attendee_id" type="row"/>
<field name="question_id" type="col"/>
<field name="answer_id" type="col"/>
</pivot>
</field>
</record>
<!-- Seach view for question analysis-->
<record model="ir.ui.view" id="view_event_question_report_search">
<field name="name">event.question.report.search</field>
<field name="model">event.question.report</field>
<field name="arch" type="xml">
<search string="Questions Analysis">
<field name="event_id" string="Event"/>
<field name="attendee_id" string="Registration"/>
<group expand="1" string="Group By">
<filter string="Event" context="{'group_by': 'event_id'}" />
<filter string="Question" context="{'group_by': 'question_id'}"/>
<filter string="Answer" context="{'group_by': 'answer_id'}" />
</group>
</search>
</field>
</record>
<!-- Action for reporting -->
<record model="ir.actions.act_window" id="action_event_question_report">
<field name="name">Questions Analysis</field>
<field name="res_model">event.question.report</field>
<field name="view_type">form</field>
<field name="view_mode">pivot,graph</field>
</record>
<!-- Menu -->
<menuitem name="Questions Analysis" id="menu_report_event_questions"
parent="event.menu_reporting_events" action="action_event_question_report" sequence="4"/>
</data>
</openerp>
@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
from openerp import fields, models, tools
class ReportEventRegistrationQuestions(models.Model):
_name = "event.question.report"
_auto = False
attendee_id = fields.Many2one(comodel_name='event.registration', string='Registration')
question_id = fields.Many2one(comodel_name='event.question', string='Question')
answer_id = fields.Many2one(comodel_name='event.answer', string='Answer')
event_id = fields.Many2one(comodel_name='event.event', string='Event')
def init(self, cr):
""" Event Question main report """
tools.drop_view_if_exists(cr, 'event_question_report')
cr.execute(""" CREATE VIEW event_question_report AS (
SELECT
att_answer.id as id,
att_answer.event_registration_id as attendee_id,
answer.question_id as question_id,
answer.id as answer_id,
question.event_id as event_id
FROM
event_registration_answer as att_answer
LEFT JOIN
event_answer as answer ON answer.id = att_answer.event_answer_id
LEFT JOIN
event_question as question ON question.id = answer.question_id
GROUP BY
attendee_id,
event_id,
question_id,
answer_id,
att_answer.id
)""")
@@ -0,0 +1,7 @@
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_question_report_all,event.question.report.all,model_event_question_report,event.group_event_user,1,1,1,1
event_registration_answer_all,access_event_registration_answer,model_event_registration_answer,,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 event_question_all event.question.all model_event_question 1 0 0 0
3 event_question_event_user event.question.event.user model_event_question event.group_event_user 1 1 1 1
4 event_answer_all event.answer.all model_event_answer 1 0 0 0
5 event_answer_event_user event.answer.event.user model_event_answer event.group_event_user 1 1 1 1
6 event_question_report_all event.question.report.all model_event_question_report event.group_event_user 1 1 1 1
7 event_registration_answer_all access_event_registration_answer model_event_registration_answer 1 1 1 1
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="ir.ui.view" id="view_event_question_form">
<field name="name">event.question.form</field>
<field name="model">event.question</field>
<field name="arch" type="xml">
<form string="Question">
<h1><field name="title" /></h1>
<field name="is_individual"/>
<label for="is_individual"/>
<field name="answer_ids">
<tree editable="bottom">
<field name="sequence" widget="handle" />
<field name="name"/>
</tree>
</field>
</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="arch" type="xml">
<form string="Question">
<group>
<field name="name"/>
</group>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_event_form_inherit_question">
<field name="name">event.form.inherit.question</field>
<field name="model">event.event</field>
<field name="inherit_id" ref="event.view_event_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//notebook" position="inside">
<page string="Questions">
<field name="question_ids" nolabel="1">
<tree>
<field name="sequence" widget="handle" />
<field name="title"/>
<field name="is_individual"/>
<field name="answer_ids"/>
</tree>
</field>
</page>
</xpath>
</data>
</field>
</record>
<record model="ir.ui.view" id="view_event_registration_form_inherit_question">
<field name="name">event.registration.form.inherit.question</field>
<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" readonly="1"/>
</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>
</field>
</record>
</odoo>
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="registration_attendee_details_questions" inherit_id="website_event.registration_attendee_details" name="Registration Attendee Details with questions">
<data>
<!-- Attendee specific questions -->
<xpath expr="//t[@name='attendee_loop']" position="inside">
<t t-if="event.specific_question_ids">
<div class="row mb32">
<div class="col-md-4" t-foreach='event.specific_question_ids' t-as='question'>
<div class="input-group input-group-sm">
<span class="input-group-addon"><t t-esc="question.title"/></span>
<select t-attf-name="#{counter}-answer_ids-#{question.id}" class="form-control" required="true">
<t t-foreach='question.answer_ids' t-as="answer">
<option t-esc='answer.name' t-att-value='answer.id'/>
</t>
</select>
</div>
</div>
</div>
</t>
</xpath>
<!-- Generic questions -->
<xpath expr="//div[@class='container']/t[last()]" position="after">
<t t-if="event.general_question_ids">
<h4 class="page-header mt32"><strong>Last questions</strong></h4>
<div class="row">
<div class="col-md-4" t-foreach='event.general_question_ids' t-as='question'>
<div class="input-group">
<span class="input-group-addon"><t t-esc='question.title'/></span>
<select t-attf-name="0-answer_ids-#{question.id}" class="form-control" required="true">
<t t-foreach='question.answer_ids' t-as="answer">
<option t-esc='answer.name' t-att-value='answer.id'/>
</t>
</select>
</div>
</div>
</div>
</t>
</xpath>
</data>
</template>
</odoo>