[MIG] survey: migrate into new api

This commit is contained in:
Ravi Gadhia
2016-07-15 12:57:51 +02:00
committed by Jérome Maes
parent 628c1d9564
commit 66cb880b74
7 changed files with 748 additions and 990 deletions
+59 -90
View File
@@ -4,30 +4,26 @@
import json
import logging
import werkzeug
import werkzeug.utils
from datetime import datetime
from math import ceil
from openerp import SUPERUSER_ID
from openerp.addons.web import http
from openerp.addons.web.http import request
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT as DTF, ustr
from odoo import fields, http, SUPERUSER_ID
from odoo.http import request
from odoo.tools import ustr
_logger = logging.getLogger(__name__)
class WebsiteSurvey(http.Controller):
# HELPER METHODS #
## HELPER METHODS ##
def _check_bad_cases(self, cr, uid, request, survey_obj, survey, user_input_obj, token=None, context=None):
def _check_bad_cases(self, survey, token=None):
# In case of bad survey, redirect to surveys list
if survey_obj.exists(cr, SUPERUSER_ID, survey.id, context=context) == []:
if not survey.sudo().exists():
return werkzeug.utils.redirect("/survey/")
# In case of auth required, block public user
if survey.auth_required and uid == request.website.user_id.id:
if survey.auth_required and request.env.user == request.website.user_id:
return request.website.render("survey.auth_required", {'survey': survey, 'token': token})
# In case of non open surveys
@@ -41,16 +37,16 @@ class WebsiteSurvey(http.Controller):
# Everything seems to be ok
return None
def _check_deadline(self, cr, uid, user_input, context=None):
def _check_deadline(self, user_input):
'''Prevent opening of the survey if the deadline has turned out
! This will NOT disallow access to users who have already partially filled the survey !'''
if user_input.deadline:
dt_deadline = datetime.strptime(user_input.deadline, DTF)
deadline = user_input.deadline
if deadline:
dt_deadline = fields.Datetime.from_string(deadline)
dt_now = datetime.now()
if dt_now > dt_deadline: # survey is not open anymore
return request.website.render("survey.notopen")
return None
## ROUTES HANDLERS ##
@@ -60,41 +56,34 @@ class WebsiteSurvey(http.Controller):
'/survey/start/<model("survey.survey"):survey>/<string:token>'],
type='http', auth='public', website=True)
def start_survey(self, survey, token=None, **post):
cr, uid, context = request.cr, request.uid, request.context
survey_obj = request.registry['survey.survey']
user_input_obj = request.registry['survey.user_input']
UserInput = request.env['survey.user_input']
# Test mode
if token and token == "phantom":
_logger.info("[survey] Phantom mode")
user_input_id = user_input_obj.create(cr, uid, {'survey_id': survey.id, 'test_entry': True}, context=context)
user_input = user_input_obj.browse(cr, uid, [user_input_id], context=context)[0]
user_input = UserInput.create({'survey_id': survey.id, 'test_entry': True})
data = {'survey': survey, 'page': None, 'token': user_input.token}
return request.website.render('survey.survey_init', data)
# END Test mode
# Controls if the survey can be displayed
errpage = self._check_bad_cases(cr, uid, request, survey_obj, survey, user_input_obj, token=token, context=context)
errpage = self._check_bad_cases(survey, token=token)
if errpage:
return errpage
# Manual surveying
if not token:
vals = {'survey_id': survey.id}
if request.website.user_id.id != uid:
vals['partner_id'] = request.registry['res.users'].browse(cr, uid, uid, context=context).partner_id.id
user_input_id = user_input_obj.create(cr, uid, vals, context=context)
user_input = user_input_obj.browse(cr, uid, [user_input_id], context=context)[0]
if request.website.user_id != request.env.user:
vals['partner_id'] = request.env.user.partner_id.id
user_input = UserInput.create(vals)
else:
try:
user_input_id = user_input_obj.search(cr, SUPERUSER_ID, [('token', '=', token)], context=context)[0]
except IndexError: # Invalid token
user_input = UserInput.sudo().search([('token', '=', token)], limit=1)
if not user_input:
return request.website.render("website.403")
else:
user_input = user_input_obj.browse(cr, SUPERUSER_ID, [user_input_id], context=context)[0]
# Do not open expired survey
errpage = self._check_deadline(cr, uid, user_input, context=context)
errpage = self._check_deadline(user_input)
if errpage:
return errpage
@@ -111,32 +100,29 @@ class WebsiteSurvey(http.Controller):
type='http', auth='public', website=True)
def fill_survey(self, survey, token, prev=None, **post):
'''Display and validates a survey'''
cr, uid, context = request.cr, request.uid, request.context
survey_obj = request.registry['survey.survey']
user_input_obj = request.registry['survey.user_input']
Survey = request.env['survey.survey']
UserInput = request.env['survey.user_input']
# Controls if the survey can be displayed
errpage = self._check_bad_cases(cr, uid, request, survey_obj, survey, user_input_obj, context=context)
errpage = self._check_bad_cases(survey)
if errpage:
return errpage
# Load the user_input
try:
user_input_id = user_input_obj.search(cr, SUPERUSER_ID, [('token', '=', token)])[0]
user_input = UserInput.sudo().search([('token', '=', token)], limit=1)
except IndexError: # Invalid token
return request.website.render("website.403")
else:
user_input = user_input_obj.browse(cr, SUPERUSER_ID, [user_input_id], context=context)[0]
# Do not display expired survey (even if some pages have already been
# displayed -- There's a time for everything!)
errpage = self._check_deadline(cr, uid, user_input, context=context)
errpage = self._check_deadline(user_input)
if errpage:
return errpage
# Select the right page
if user_input.state == 'new': # First page
page, page_nr, last = survey_obj.next_page(cr, uid, user_input, 0, go_back=False, context=context)
page, page_nr, last = Survey.next_page(user_input, 0, go_back=False)
data = {'survey': survey, 'page': page, 'page_nr': page_nr, 'token': user_input.token}
if last:
data.update({'last': True})
@@ -147,11 +133,11 @@ class WebsiteSurvey(http.Controller):
'user_input': user_input})
elif user_input.state == 'skip':
flag = (True if prev and prev == 'prev' else False)
page, page_nr, last = survey_obj.next_page(cr, uid, user_input, user_input.last_displayed_page_id.id, go_back=flag, context=context)
page, page_nr, last = Survey.next_page(user_input, user_input.last_displayed_page_id.id, go_back=flag)
#special case if you click "previous" from the last page, then leave the survey, then reopen it from the URL, avoid crash
if not page:
page, page_nr, last = survey_obj.next_page(cr, uid, user_input, user_input.last_displayed_page_id.id, go_back=True, context=context)
page, page_nr, last = Survey.next_page(user_input, user_input.last_displayed_page_id.id, go_back=True)
data = {'survey': survey, 'page': page, 'page_nr': page_nr, 'token': user_input.token}
if last:
@@ -165,16 +151,14 @@ class WebsiteSurvey(http.Controller):
'/survey/prefill/<model("survey.survey"):survey>/<string:token>/<model("survey.page"):page>'],
type='http', auth='public', website=True)
def prefill(self, survey, token, page=None, **post):
cr, uid, context = request.cr, request.uid, request.context
user_input_line_obj = request.registry['survey.user_input_line']
UserInputLine = request.env['survey.user_input_line']
ret = {}
# Fetch previous answers
if page:
ids = user_input_line_obj.search(cr, SUPERUSER_ID, [('user_input_id.token', '=', token), ('page_id', '=', page.id)], context=context)
previous_answers = UserInputLine.sudo().search([('user_input_id.token', '=', token), ('page_id', '=', page.id)])
else:
ids = user_input_line_obj.search(cr, SUPERUSER_ID, [('user_input_id.token', '=', token)], context=context)
previous_answers = user_input_line_obj.browse(cr, uid, ids, context=context)
previous_answers = UserInputLine.sudo().search([('user_input_id.token', '=', token)])
# Return non empty answers in a JSON compatible format
for answer in previous_answers:
@@ -199,7 +183,7 @@ class WebsiteSurvey(http.Controller):
answer_tag = "%s_%s" % (answer_tag, answer.value_suggested_row.id)
answer_value = answer.value_suggested.id
if answer_value:
dict_soft_update(ret, answer_tag, answer_value)
ret.setdefault(answer_tag, []).append(answer_value)
else:
_logger.warning("[survey] No answer has been found for question %s marked as non skipped" % answer_tag)
return json.dumps(ret)
@@ -208,13 +192,10 @@ class WebsiteSurvey(http.Controller):
@http.route(['/survey/scores/<model("survey.survey"):survey>/<string:token>'],
type='http', auth='public', website=True)
def get_scores(self, survey, token, page=None, **post):
cr, uid, context = request.cr, request.uid, request.context
user_input_line_obj = request.registry['survey.user_input_line']
ret = {}
# Fetch answers
ids = user_input_line_obj.search(cr, SUPERUSER_ID, [('user_input_id.token', '=', token)], context=context)
previous_answers = user_input_line_obj.browse(cr, uid, ids, context=context)
previous_answers = request.env['survey.user_input_line'].sudo().search([('user_input_id.token', '=', token)])
# Compute score for each question
for answer in previous_answers:
@@ -223,50 +204,42 @@ class WebsiteSurvey(http.Controller):
return json.dumps(ret)
# AJAX submission of a page
@http.route(['/survey/submit/<model("survey.survey"):survey>'],
type='http', methods=['POST'], auth='public', website=True)
@http.route(['/survey/submit/<model("survey.survey"):survey>'], type='http', methods=['POST'], auth='public', website=True)
def submit(self, survey, **post):
_logger.debug('Incoming data: %s', post)
page_id = int(post['page_id'])
cr, uid, context = request.cr, request.uid, request.context
survey_obj = request.registry['survey.survey']
questions_obj = request.registry['survey.question']
questions_ids = questions_obj.search(cr, uid, [('page_id', '=', page_id)], context=context)
questions = questions_obj.browse(cr, uid, questions_ids, context=context)
questions = request.env['survey.question'].search([('page_id', '=', page_id)])
# Answer validation
errors = {}
for question in questions:
answer_tag = "%s_%s_%s" % (survey.id, page_id, question.id)
errors.update(questions_obj.validate_question(cr, uid, question, post, answer_tag, context=context))
errors.update(question.validate_question(post, answer_tag))
ret = {}
if (len(errors) != 0):
if len(errors):
# Return errors messages to webpage
ret['errors'] = errors
else:
# Store answers into database
user_input_obj = request.registry['survey.user_input']
user_input_line_obj = request.registry['survey.user_input_line']
try:
user_input_id = user_input_obj.search(cr, SUPERUSER_ID, [('token', '=', post['token'])], context=context)[0]
user_input = request.env['survey.user_input'].sudo().search([('token', '=', post['token'])], limit=1)
except KeyError: # Invalid token
return request.website.render("website.403")
user_input = user_input_obj.browse(cr, SUPERUSER_ID, user_input_id, context=context)
user_id = uid if user_input.type != 'link' else SUPERUSER_ID
user_id = request.env.user.id if user_input.type != 'link' else SUPERUSER_ID
for question in questions:
answer_tag = "%s_%s_%s" % (survey.id, page_id, question.id)
user_input_line_obj.save_lines(cr, user_id, user_input_id, question, post, answer_tag, context=context)
request.env['survey.user_input_line'].sudo(user=user_id).save_lines(user_input.id, question, post, answer_tag)
go_back = post['button_submit'] == 'previous'
next_page, _, last = survey_obj.next_page(cr, uid, user_input, page_id, go_back=go_back, context=context)
next_page, _, last = request.env['survey.survey'].next_page(user_input, page_id, go_back=go_back)
vals = {'last_displayed_page_id': page_id}
if next_page is None and not go_back:
vals.update({'state': 'done'})
else:
vals.update({'state': 'skip'})
user_input_obj.write(cr, user_id, user_input_id, vals, context=context)
user_input.sudo(user=user_id).write(vals)
ret['redirect'] = '/survey/fill/%s/%s' % (survey.id, post['token'])
if go_back:
ret['redirect'] += '/prev'
@@ -289,12 +262,11 @@ class WebsiteSurvey(http.Controller):
type='http', auth='user', website=True)
def survey_reporting(self, survey, token=None, **post):
'''Display survey Results & Statistics for given survey.'''
result_template ='survey.result'
result_template = 'survey.result'
current_filters = []
filter_display_data = []
filter_finish = False
survey_obj = request.registry['survey.survey']
if not survey.user_input_ids or not [input_id.id for input_id in survey.user_input_ids if input_id.state != 'new']:
result_template = 'survey.no_result'
if 'finished' in post:
@@ -302,8 +274,8 @@ class WebsiteSurvey(http.Controller):
filter_finish = True
if post or filter_finish:
filter_data = self.get_filter_data(post)
current_filters = survey_obj.filter_input_ids(request.cr, request.uid, survey, filter_data, filter_finish, context=request.context)
filter_display_data = survey_obj.get_filter_display_data(request.cr, request.uid, filter_data, context=request.context)
current_filters = survey.filter_input_ids(filter_data, filter_finish)
filter_display_data = survey.get_filter_display_data(filter_data)
return request.website.render(result_template,
{'survey': survey,
'survey_dict': self.prepare_result_dict(survey, current_filters),
@@ -350,15 +322,21 @@ class WebsiteSurvey(http.Controller):
# filter_finish: boolean => only finished surveys or not
#
def prepare_result_dict(self,survey, current_filters=None):
def prepare_result_dict(self, survey, current_filters=None):
"""Returns dictionary having values for rendering template"""
current_filters = current_filters if current_filters else []
survey_obj = request.registry['survey.survey']
Survey = request.env['survey.survey']
result = {'page_ids': []}
for page in survey.page_ids:
page_dict = {'page': page, 'question_ids': []}
for question in page.question_ids:
question_dict = {'question':question, 'input_summary':survey_obj.get_input_summary(request.cr, request.uid, question, current_filters, context=request.context), 'prepare_result':survey_obj.prepare_result(request.cr, request.uid, question, current_filters, context=request.context), 'graph_data': self.get_graph_data(question, current_filters)}
question_dict = {
'question': question,
'input_summary': Survey.get_input_summary(question, current_filters),
'prepare_result': Survey.prepare_result(question, current_filters),
'graph_data': self.get_graph_data(question, current_filters),
}
page_dict['question_ids'].append(question_dict)
result['page_ids'].append(page_dict)
return result
@@ -384,28 +362,19 @@ class WebsiteSurvey(http.Controller):
'''Returns formatted data required by graph library on basis of filter'''
# TODO refactor this terrible method and merge it with prepare_result_dict
current_filters = current_filters if current_filters else []
survey_obj = request.registry['survey.survey']
Survey = request.env['survey.survey']
result = []
if question.type == 'multiple_choice':
result.append({'key': ustr(question.question),
'values': survey_obj.prepare_result(request.cr, request.uid, question, current_filters, context=request.context)['answers']
'values': Survey.prepare_result(question, current_filters)['answers']
})
if question.type == 'simple_choice':
result = survey_obj.prepare_result(request.cr, request.uid, question, current_filters, context=request.context)['answers']
result = Survey.prepare_result(question, current_filters)['answers']
if question.type == 'matrix':
data = survey_obj.prepare_result(request.cr, request.uid, question, current_filters, context=request.context)
data = Survey.prepare_result(question, current_filters)
for answer in data['answers']:
values = []
for row in data['rows']:
values.append({'text': data['rows'].get(row), 'count': data['result'].get((row, answer))})
result.append({'key': data['answers'].get(answer), 'values': values})
return json.dumps(result)
def dict_soft_update(dictionary, key, value):
''' Insert the pair <key>: <value> into the <dictionary>. If <key> is
already present, this function will append <value> to the list of
existing data (instead of erasing it) '''
if key in dictionary:
dictionary[key].append(value)
else:
dictionary.update({key: [value]})
File diff suppressed because it is too large Load Diff
+55 -63
View File
@@ -7,10 +7,10 @@ from collections import Counter
from itertools import product
from urlparse import urljoin
from openerp import _
from openerp.exceptions import UserError
from openerp.tests.common import TransactionCase
from openerp.addons.website.models.website import slug
from odoo import _
from odoo.exceptions import UserError
from odoo.tests.common import TransactionCase
from odoo.addons.website.models.website import slug
class TestSurvey(TransactionCase):
@@ -31,19 +31,11 @@ class TestSurvey(TransactionCase):
'name': 'Wout Janssens', 'login': 'Wout', 'alias_name': 'wout', 'email': 'wout.janssens@example.com',
'groups_id': [(6, 0, [self.ref('base.group_public')])]})
self.Partner = self.env['res.partner']
self.Survey = self.env['survey.survey']
self.Question = self.env['survey.question']
self.Stage = self.env['survey.stage']
self.SurveyMailMessage = self.env['survey.mail.compose.message']
self.UserInput = self.env['survey.user_input']
self.UserInputLine = self.env['survey.user_input_line']
self.survey1 = self.Survey.sudo(self.survey_manager).create({'title': "S0", 'page_ids': [(0, 0, {'title': "P0"})]})
self.survey1 = self.env['survey.survey'].sudo(self.survey_manager).create({'title': "S0", 'page_ids': [(0, 0, {'title': "P0"})]})
self.page1 = self.survey1.page_ids[0]
def test_00_create_minimal_survey(self):
question = self.Question.sudo(self.survey_manager).create({'page_id': self.page1.id, 'question': 'Q0'})
question = self.env['survey.question'].sudo(self.survey_manager).create({'page_id': self.page1.id, 'question': 'Q0'})
self.assertEqual(self.survey1.title, "S0", msg="Title of the survey is somehow modified.")
self.assertEqual(len(self.survey1.page_ids), 1, msg="Additional Pages are linked with the survey after creation.")
self.assertEqual(self.page1.title, "P0", msg="Title of the page is somehow modified.")
@@ -51,88 +43,88 @@ class TestSurvey(TransactionCase):
self.assertEqual(question.question, "Q0", msg="Title of the Question is somehow modified.")
def test_01_question_type_validation_save_line_function(self):
for (question_type, text) in self.Question._columns['type'].selection:
for (question_type, text) in self.env['survey.question']._columns['type'].selection:
# Each question ype must have validation function.
self.assertTrue(hasattr(self.Question, 'validate_' + question_type), msg="Question must have a validation method in\
self.assertTrue(hasattr(self.env['survey.question'], 'validate_' + question_type), msg="Question must have a validation method in\
the form of 'validate_' followed by the name of the type.")
# Survey Input Lines must have validation function for each line.
self.assertTrue(hasattr(self.UserInputLine, 'save_line_' + question_type), msg="Inputline must have Save method in \
self.assertTrue(hasattr(self.env['survey.user_input_line'], 'save_line_' + question_type), msg="Inputline must have Save method in \
the form of 'save_line_' followed by the name of the type.")
def test_02_question_answer_required(self):
for (question_type, text) in self.Question._columns['type'].selection:
for (question_type, text) in self.env['survey.question']._columns['type'].selection:
# Blank value of field is not accepted for mandatory questions.
if question_type == 'multiple_choice':
question = self.Question.sudo(self.survey_manager).create({
question = self.env['survey.question'].sudo(self.survey_manager).create({
'page_id': self.page1.id, 'question': 'Q0', 'type': 'multiple_choice',
'constr_mandatory': True, 'constr_error_msg': 'Error',
'labels_ids': [(0, 0, {'value': "MChoice0", "quizz_mark": 0}), (0, 0, {'value': "MChoice1", "quizz_mark": 0})]})
elif question_type == 'matrix':
question = self.Question.sudo(self.survey_manager).create({
question = self.env['survey.question'].sudo(self.survey_manager).create({
'page_id': self.page1.id, 'question': 'Q0', 'type': 'matrix', 'matrix_subtype': 'simple',
'constr_mandatory': True, 'constr_error_msg': 'Error',
'labels_ids': [(0, 0, {'value': "Column0", "quizz_mark": 0}), (0, 0, {'value': "Column1", "quizz_mark": 0})],
'labels_ids_2': [(0, 0, {'value': "Row0", "quizz_mark": 0}), (0, 0, {'value': "Row1", "quizz_mark": 0})]})
else:
question = self.Question.sudo(self.survey_manager).create({
question = self.env['survey.question'].sudo(self.survey_manager).create({
'page_id': self.page1.id, 'question': 'Q0', 'type': question_type, 'constr_mandatory': True, 'constr_error_msg': 'Error'})
answer_tag = '%s_%s_%s' % (self.survey1.id, self.page1.id, question.id)
self.assertDictEqual({answer_tag: "Error"}, self.Question.validate_question(question, {answer_tag: ''}, answer_tag),
self.assertDictEqual({answer_tag: "Error"}, question.validate_question({answer_tag: ''}, answer_tag),
msg=("Validation function for type %s is unable to generate error if it is mandatory and answer is blank." % question_type))
def test_03_question_textbox(self):
questions = [
self.Question.sudo(self.survey_manager).create({
self.env['survey.question'].sudo(self.survey_manager).create({
'page_id': self.page1.id, 'question': 'Q0', 'type': 'textbox', 'validation_email': True}),
self.Question.sudo(self.survey_manager).create({
self.env['survey.question'].sudo(self.survey_manager).create({
'page_id': self.page1.id, 'question': 'Q1', 'type': 'textbox', 'validation_required': True,
'validation_length_min': 2, 'validation_length_max': 8, 'validation_error_msg': "Error"})]
results = [('test @ testcom', _('This answer must be an email address')), ('t', 'Error')]
for i in range(len(questions)):
answer_tag = '%s_%s_%s' % (self.survey1.id, self.page1.id, questions[i].id)
self.assertEqual(self.Question.validate_question(questions[i], {answer_tag: results[i][0]}, answer_tag), {answer_tag: results[i][1]}, msg="\
self.assertEqual(questions[i].validate_question({answer_tag: results[i][0]}, answer_tag), {answer_tag: results[i][1]}, msg="\
Validation function for textbox is unable to notify if answer is violating the validation rules")
def test_04_question_numerical_box(self):
question = self.Question.sudo(self.survey_manager).create({
question = self.env['survey.question'].sudo(self.survey_manager).create({
'page_id': self.page1.id, 'question': 'Q0', 'type': 'numerical_box', 'validation_required': True,
'validation_min_float_value': 2.1, 'validation_max_float_value': 3.0, 'validation_error_msg': "Error"})
answer_tag = '%s_%s_%s' % (self.survey1.id, self.page1.id, question.id)
results = [('aaa', _('This is not a number')), ('4.5', 'Error'), ('0.1', 'Error')]
for i in range(len(results)):
self.assertEqual(self.Question.validate_question(question, {answer_tag: results[i][0]}, answer_tag), {answer_tag: results[i][1]}, msg="\
self.assertEqual(question.validate_question({answer_tag: results[i][0]}, answer_tag), {answer_tag: results[i][1]}, msg="\
Validation function for type numerical_box is unable to notify if answer is violating the validation rules")
def test_05_question_datetime(self):
question = self.Question.sudo(self.survey_manager).create({
question = self.env['survey.question'].sudo(self.survey_manager).create({
'page_id': self.page1.id, 'question': 'Q0', 'type': 'datetime', 'validation_required': True,
'validation_min_date': '2015-03-20 00:00:00', 'validation_max_date': '2015-03-25 00:00:00', 'validation_error_msg': "Error"})
answer_tag = '%s_%s_%s' % (self.survey1.id, self.page1.id, question.id)
results = [('2015-55-10', _('This is not a date/time')), ('2015-03-19 00:00:00', 'Error'), ('2015-03-26 00:00:00', 'Error')]
for i in range(len(results)):
self.assertEqual(self.Question.validate_question(question, {answer_tag: results[i][0]}, answer_tag), {answer_tag: results[i][1]}, msg="\
self.assertEqual(question.validate_question({answer_tag: results[i][0]}, answer_tag), {answer_tag: results[i][1]}, msg="\
Validation function for type datetime is unable to notify if answer is violating the validation rules")
def test_06_survey_sharing(self):
# Case-1: Executing action with correct data.
correct_survey = self.Survey.sudo(self.survey_manager).create({
'title': "S0", 'stage_id': self.Stage.search([('sequence', '=', 1)]).id,
correct_survey = self.env['survey.survey'].sudo(self.survey_manager).create({
'title': "S0", 'stage_id': self.env['survey.stage'].search([('sequence', '=', 1)]).id,
'page_ids': [(0, 0, {'title': "P0", 'question_ids': [(0, 0, {'question': "Q0", 'type': 'free_text'})]})]})
action = correct_survey.action_send_survey()
templates = self.env['ir.model.data'].get_object_reference('survey', 'email_template_survey')
template_id = templates[1] if len(templates) > 0 else False
ctx = dict(self.env.context)
ctx.update({
'default_model': 'survey.survey',
'default_res_id': correct_survey.id,
'default_survey_id': correct_survey.id,
'default_use_template': bool(template_id),
'default_template_id': template_id,
'default_composition_mode': 'comment'})
template = self.env.ref('survey.email_template_survey', raise_if_not_found=False)
ctx = dict(
self.env.context,
default_model='survey.survey',
default_res_id=correct_survey.id,
default_survey_id=correct_survey.id,
default_use_template=bool(template),
default_template_id=template and template.id or False,
default_composition_mode='comment')
self.assertDictEqual(action, {
'type': 'ir.actions.act_window',
@@ -145,26 +137,26 @@ class TestSurvey(TransactionCase):
# Case-2: Executing action with incorrect data.
surveys = [
self.Survey.sudo(self.survey_manager).create({ # Survey without any page or question.
self.env['survey.survey'].sudo(self.survey_manager).create({ # Survey without any page or question.
'title': "Test survey"}),
self.Survey.sudo(self.survey_manager).create({ # Closed Survey.
'title': "S0", 'stage_id': self.Stage.search([('closed', '=', True)]).id, # Getting Closed stage id.
self.env['survey.survey'].sudo(self.survey_manager).create({ # Closed Survey.
'title': "S0", 'stage_id': self.env['survey.stage'].search([('closed', '=', True)]).id, # Getting Closed stage id.
'page_ids': [(0, 0, {'title': "P0", 'question_ids': [(0, 0, {'question': "Q0", 'type': 'free_text'})]})]})]
for survey in surveys:
self.assertRaises(UserError, survey.action_send_survey)
def test_07_survey_email_message(self):
# Case-1: Executing send_mail with correct data.
partner = self.Partner.create({'name': 'Marie De Cock', 'email': 'marie.de.cock@gmail.com'})
survey_mail_message = self.SurveyMailMessage.sudo(self.survey_manager).create({
partner = self.env['res.partner'].create({'name': 'Marie De Cock', 'email': 'marie.de.cock@gmail.com'})
survey_mail_message = self.env['survey.mail.compose.message'].sudo(self.survey_manager).create({
'survey_id': self.survey1.id, 'public': 'email_public_link', 'body': '__URL__', 'partner_ids': [(4, partner.id)]})
survey_mail_message.send_mail()
# Case-2: Executing send_mail with incorrect data.
mail_messages = [
self.SurveyMailMessage.sudo(self.survey_manager).create({ # Mail Message without __URL__ in body.
self.env['survey.mail.compose.message'].sudo(self.survey_manager).create({ # Mail Message without __URL__ in body.
'survey_id': self.survey1.id, 'public': 'email_public_link'}),
self.SurveyMailMessage.sudo(self.survey_manager).create({ # Mail Message without recipents.
self.env['survey.mail.compose.message'].sudo(self.survey_manager).create({ # Mail Message without recipents.
'survey_id': self.survey1.id, 'public': 'email_public_link', 'body': "__URL__"})]
for message in mail_messages:
self.assertRaises(UserError, message.send_mail)
@@ -198,20 +190,20 @@ class TestSurvey(TransactionCase):
self.assertEqual(url_html % ('/' + url), getattr(self.survey1.with_context({'relative_url': True}), urltype + '_url_html'), msg="Public URL is incorrect.")
def test_09_answer_survey(self):
question = self.Question.sudo(self.survey_manager).create({'page_id': self.page1.id, 'question': 'Q0'})
input_portal = self.UserInput.sudo(self.survey_user).create({
question = self.env['survey.question'].sudo(self.survey_manager).create({'page_id': self.page1.id, 'question': 'Q0'})
input_portal = self.env['survey.user_input'].sudo(self.survey_user).create({
'survey_id': self.survey1.id,
'partner_id': self.survey_user.partner_id.id,
'user_input_line_ids': [(0, 0, {
'skipped': False, 'answer_type': 'free_text', 'value_free_text': "Test Answer",
'survey_id': self.survey1.id, 'page_id': self.page1, 'question_id': question.id})]})
'survey_id': self.survey1.id, 'question_id': question.id})]})
input_public = self.UserInput.sudo(self.user_public).create({
input_public = self.env['survey.user_input'].sudo(self.user_public).create({
'survey_id': self.survey1.id,
'partner_id': self.survey_user.partner_id.id,
'user_input_line_ids': [(0, 0, {
'skipped': False, 'answer_type': 'free_text', 'value_free_text': "Test Answer",
'survey_id': self.survey1.id, 'page_id': self.page1, 'question_id': question.id})]})
'survey_id': self.survey1.id, 'question_id': question.id})]})
answers = [input_portal.user_input_line_ids[0], input_public.user_input_line_ids[0]]
expected_values = {'answer_type': 'free_text', 'value_free_text': "Test Answer"}
@@ -220,52 +212,52 @@ class TestSurvey(TransactionCase):
self.assertEqual(getattr(answer, field), value, msg="Unable to answer the survey. Expected behaviour of %s is not proper." % (field))
def test_10_survey_result_simple_multiple_choice(self):
question = self.Question.sudo(self.survey_manager).create({
question = self.env['survey.question'].sudo(self.survey_manager).create({
'page_id': self.page1.id, 'question': 'Q0', 'type': 'simple_choice',
'labels_ids': [(0, 0, {'value': "Choice0", 'quizz_mark': 0}), (0, 0, {'value': "Choice1", 'quizz_mark': 0})]})
for i in range(3):
self.UserInput.sudo(self.user_public).create({'survey_id': self.survey1.id, 'user_input_line_ids': [(0, 0, {
self.env['survey.user_input'].sudo(self.user_public).create({'survey_id': self.survey1.id, 'user_input_line_ids': [(0, 0, {
'question_id': question.id,
'answer_type': 'suggestion',
'value_suggested': random.choice(question.labels_ids.ids)})]})
lines = [line.value_suggested.id for line in question.user_input_line_ids]
answers = [{'text': label.value, 'count': lines.count(label.id), 'answer_id': label.id} for label in question.labels_ids]
prp_result = self.Survey.prepare_result(question)['answers']
prp_result = self.env['survey.survey'].prepare_result(question)['answers']
answers.sort()
prp_result.sort()
self.assertEqual(prp_result, answers, msg="Statistics of simple, multiple choice questions are different from expectation")
def test_11_survey_result_matrix(self):
question = self.Question.sudo(self.survey_manager).create({
question = self.env['survey.question'].sudo(self.survey_manager).create({
'page_id': self.page1.id, 'question': 'Q0', 'type': 'matrix', 'matrix_subtype': 'simple',
'labels_ids': [(0, 0, {'value': "Column0", "quizz_mark": 0}), (0, 0, {'value': "Column1", "quizz_mark": 0})],
'labels_ids_2': [(0, 0, {'value': "Row0", "quizz_mark": 0}), (0, 0, {'value': "Row1", "quizz_mark": 0})]})
for i in range(3):
self.UserInput.sudo(self.user_public).create({'survey_id': self.survey1.id, 'user_input_line_ids': [(0, 0, {
self.env['survey.user_input'].sudo(self.user_public).create({'survey_id': self.survey1.id, 'user_input_line_ids': [(0, 0, {
'question_id': question.id, 'answer_type': 'suggestion', 'value_suggested': random.choice(question.labels_ids.ids),
'value_suggested_row': random.choice(question.labels_ids_2.ids)})]})
lines = [(line.value_suggested_row.id, line.value_suggested.id) for line in question.user_input_line_ids]
res = {}
for i in product(question.labels_ids_2.ids, question.labels_ids.ids):
res[i] = lines.count((i))
self.assertEqual(self.Survey.prepare_result(question)['result'], res, msg="Statistics of matrix type questions are different from expectations")
self.assertEqual(self.env['survey.survey'].prepare_result(question)['result'], res, msg="Statistics of matrix type questions are different from expectations")
def test_12_survey_result_numeric_box(self):
question = self.Question.sudo(self.survey_manager).create({'page_id': self.page1.id, 'question': 'Q0', 'type': 'numerical_box'})
question = self.env['survey.question'].sudo(self.survey_manager).create({'page_id': self.page1.id, 'question': 'Q0', 'type': 'numerical_box'})
num = map(float, random.sample(range(1, 100), 3))
nsum = sum(num)
for i in range(3):
self.UserInput.sudo(self.user_public).create({'survey_id': self.survey1.id, 'user_input_line_ids': [(0, 0, {
self.env['survey.user_input'].sudo(self.user_public).create({'survey_id': self.survey1.id, 'user_input_line_ids': [(0, 0, {
'question_id': question.id, 'answer_type': 'number', 'value_number': num[i]})]})
exresult = {
'average': round((nsum / len(num)), 2), 'max': round(max(num), 2),
'min': round(min(num), 2), 'sum': nsum, 'most_common': Counter(num).most_common(5)}
result = self.Survey.prepare_result(question)
result = self.env['survey.survey'].prepare_result(question)
for key in exresult.keys():
self.assertEqual(result[key], exresult[key], msg="Statistics of numeric box type questions are different from expectations")
def test_13_survey_actions(self):
self.Question.sudo(self.survey_manager).create({'page_id': self.page1.id, 'question': 'Q0', 'type': 'numerical_box'})
self.env['survey.question'].sudo(self.survey_manager).create({'page_id': self.page1.id, 'question': 'Q0', 'type': 'numerical_box'})
actions = {
'start': {'method': 'public', 'token': '/test', 'text': 'Start'},
+4 -4
View File
@@ -1,10 +1,10 @@
import openerp.tests
import odoo.tests
# Part of Odoo. See LICENSE file for full copyright and licensing details.
@openerp.tests.common.at_install(False)
@openerp.tests.common.post_install(True)
class TestUi(openerp.tests.HttpCase):
@odoo.tests.common.at_install(False)
@odoo.tests.common.post_install(True)
class TestUi(odoo.tests.HttpCase):
def test_01_admin_survey_tour(self):
self.phantom_js("/", "odoo.__DEBUG__.services['web.Tour'].run('test_survey', 'test')", "odoo.__DEBUG__.services['web.Tour'].tours.test_survey", login="admin")
+3 -4
View File
@@ -29,7 +29,7 @@
<field name="arch" type="xml">
<form string="Survey User inputs" create="false">
<header>
<button name="action_survey_resent" string="Sent Invitation Again" type="object" class="oe_highlight" attrs="{'invisible': ['|', ('type','=','manually'), ('state', '=', 'done')]}"/>
<button name="action_survey_resend" string="Send Invitation Again" type="object" class="oe_highlight" attrs="{'invisible': ['|', ('type','=','manually'), ('state', '=', 'done')]}"/>
<button name="action_view_answers" states="done" string="Print These Answers" type="object" class="oe_highlight" />
<button name="action_survey_results" string="View Results" type="object" />
<field name="state" widget="statusbar"/>
@@ -401,7 +401,7 @@
</group>
<notebook>
<page string="Answers">
<field name="validation_email" attrs="{'invisible': [('type', '!=', 'textbox')]}" on_change="onchange_validation_email(validation_email)"/>
<field name="validation_email" attrs="{'invisible': [('type', '!=', 'textbox')]}"/>
<label for="validation_email" attrs="{'invisible': [('type', '!=', 'textbox')]}"/>
<separator />
<field name="labels_ids" string="Type of answers" context="{'default_question_id': active_id}" attrs="{'invisible': [('type', 'not in', ['simple_choice', 'multiple_choice', 'matrix'])]}">
@@ -622,8 +622,7 @@
<field name="state">code</field>
<field name="code">
if context.get('active_ids'):
test_answers = self.pool['survey.user_input'].search(cr, uid, [('survey_id', 'in', context['active_ids']), ('test_entry', '=', 'True')], context=context)
self.pool['survey.user_input'].unlink(cr, uid, test_answers, context=context)
test_answers = env['survey.user_input'].search([('survey_id', 'in', context['active_ids']), ('test_entry', '=', 'True')]).unlink()
</field>
</record>
@@ -1,121 +1,85 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import osv
from openerp.osv import fields
from openerp.tools.translate import _
from datetime import datetime
from openerp.exceptions import UserError
import re
import uuid
import urlparse
from odoo import api, fields, models, _
from odoo.exceptions import UserError
emails_split = re.compile(r"[;,\n\r]+")
email_validator = re.compile(r"[^@]+@[^@]+\.[^@]+")
class survey_mail_compose_message(osv.TransientModel):
class SurveyMailComposeMessage(models.TransientModel):
_name = 'survey.mail.compose.message'
_inherit = 'mail.compose.message'
_description = 'Email composition wizard for Survey'
_log_access = True
def _get_public_url(self, cr, uid, ids, name, arg, context=None):
res = dict((id, 0) for id in ids)
survey_obj = self.pool.get('survey.survey')
for wizard in self.browse(cr, uid, ids, context=context):
res[wizard.id] = wizard.survey_id.public_url
return res
def default_survey_id(self):
context = self.env.context
if context.get('model') == 'survey.survey':
return context.get('res_id')
def _get_public_url_html(self, cr, uid, ids, name, arg, context=None):
""" Compute if the message is unread by the current user """
urls = self._get_public_url(cr, uid, ids, name, arg, context=context)
for key, url in urls.items():
urls[key] = '<a href="%s">%s</a>' % (url, _("Click here to start survey"))
return urls
survey_id = fields.Many2one('survey.survey', string='Survey', default=default_survey_id, required=True)
public = fields.Selection([('public_link', 'Share the public web link to your audience.'),
('email_public_link', 'Send by email the public web link to your audience.'),
('email_private', 'Send private invitation to your audience (only one response per recipient and per invitation).')],
string='Share options', default='public_link', required=True)
public_url = fields.Char(compute="_compute_survey_url", string="Public url")
public_url_html = fields.Char(compute="_compute_survey_url", string="Public HTML web link")
partner_ids = fields.Many2many('res.partner', 'survey_mail_compose_message_res_partner_rel', 'wizard_id', 'partner_id', string='Existing contacts')
attachment_ids = fields.Many2many('ir.attachment', 'survey_mail_compose_message_ir_attachments_rel', 'wizard_id', 'attachment_id', string='Attachments')
multi_email = fields.Text(string='List of emails', help="This list of emails of recipients will not be converted in contacts.\
Emails must be separated by commas, semicolons or newline.")
date_deadline = fields.Date(string="Deadline to which the invitation to respond is valid",
help="Deadline to which the invitation to respond for this survey is valid. If the field is empty,\
the invitation is still valid.")
_columns = {
'survey_id': fields.many2one('survey.survey', 'Survey', required=True),
'public': fields.selection([('public_link', 'Share the public web link to your audience.'),
('email_public_link', 'Send by email the public web link to your audience.'),
('email_private', 'Send private invitation to your audience (only one response per recipient and per invitation).')],
string='Share options', required=True),
'public_url': fields.function(_get_public_url, string="Public url", type="char"),
'public_url_html': fields.function(_get_public_url_html, string="Public HTML web link", type="char"),
'partner_ids': fields.many2many('res.partner',
'survey_mail_compose_message_res_partner_rel',
'wizard_id', 'partner_id', 'Existing contacts'),
'attachment_ids': fields.many2many('ir.attachment',
'survey_mail_compose_message_ir_attachments_rel',
'wizard_id', 'attachment_id', 'Attachments'),
'multi_email': fields.text(string='List of emails', help="This list of emails of recipients will not converted in contacts. Emails separated by commas, semicolons or newline."),
'date_deadline': fields.date(string="Deadline to which the invitation to respond is valid", help="Deadline to which the invitation to respond for this survey is valid. If the field is empty, the invitation is still valid."),
}
@api.depends('survey_id')
def _compute_survey_url(self):
for wizard in self:
wizard.public_url = wizard.survey_id.public_url
wizard.public_url_html = wizard.survey_id.public_url_html
_defaults = {
'public': 'public_link',
'survey_id': lambda self, cr, uid, ctx={}: ctx.get('model') == 'survey.survey' and ctx.get('res_id') or None
}
def default_get(self, cr, uid, fields, context=None):
res = super(survey_mail_compose_message, self).default_get(cr, uid, fields, context=context)
@api.model
def default_get(self, fields):
res = super(SurveyMailComposeMessage, self).default_get(fields)
context = self.env.context
if context.get('active_model') == 'res.partner' and context.get('active_ids'):
res.update({'partner_ids': context.get('active_ids')})
res.update({'partner_ids': context['active_ids']})
return res
def onchange_multi_email(self, cr, uid, ids, multi_email, context=None):
emails = list(set(emails_split.split(multi_email or "")))
@api.onchange('multi_email')
def onchange_multi_email(self):
emails = list(set(emails_split.split(self.multi_email or "")))
emails_checked = []
error_message = ""
for email in emails:
email = email.strip()
if email:
if not re.search(r"^[^@]+@[^@]+$", email):
if not email_validator.match(email):
error_message += "\n'%s'" % email
else:
emails_checked.append(email)
if error_message:
raise UserError(_("One email at least is incorrect: %s") % error_message)
raise UserError(_("Incorrect Email Address: %s") % error_message)
emails_checked.sort()
values = {'multi_email': '\n'.join(emails_checked)}
return {'value': values}
def onchange_survey_id(self, cr, uid, ids, survey_id, context=None):
""" Compute if the message is unread by the current user. """
if survey_id:
survey = self.pool.get('survey.survey').browse(cr, uid, survey_id, context=context)
return {
'value': {
'subject': survey.title,
'public_url': survey.public_url,
'public_url_html': '<a href="%s">%s</a>' % (survey.public_url, _("Click here to take survey")),
}}
else:
txt = _("Please select a survey")
return {
'value': {
'public_url': txt,
'public_url_html': txt,
}}
self.multi_email = '\n'.join(emails_checked)
#------------------------------------------------------
# Wizard validation and send
#------------------------------------------------------
def send_mail(self, cr, uid, ids, auto_commit=False, context=None):
@api.multi
def send_mail(self, auto_commit=False):
""" Process the wizard content and proceed with sending the related
email(s), rendering any template patterns on the fly if needed """
if context is None:
context = {}
survey_response_obj = self.pool.get('survey.user_input')
partner_obj = self.pool.get('res.partner')
mail_mail_obj = self.pool.get('mail.mail')
try:
model, anonymous_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'portal', 'group_anonymous')
except ValueError:
anonymous_id = None
SurveyUserInput = self.env['survey.user_input']
Partner = self.env['res.partner']
Mail = self.env['mail.mail']
anonymous_group = self.env.ref('portal.group_anonymous', raise_if_not_found=False)
def create_response_and_send_mail(wizard, token, partner_id, email):
""" Create one mail by recipients and replace __URL__ by link with identification token """
@@ -143,37 +107,38 @@ class survey_mail_compose_message(osv.TransientModel):
values['recipient_ids'] = [(4, partner_id)]
else:
values['email_to'] = email
mail_id = mail_mail_obj.create(cr, uid, values, context=context)
mail_mail_obj.send(cr, uid, [mail_id], context=context)
Mail.create(values).send()
def create_token(wizard, partner_id, email):
if context.get("survey_resent_token"):
response_ids = survey_response_obj.search(cr, uid, [('survey_id', '=', wizard.survey_id.id), ('state', 'in', ['new', 'skip']), '|', ('partner_id', '=', partner_id), ('email', '=', email)], context=context)
if response_ids:
return survey_response_obj.read(cr, uid, response_ids, ['token'], context=context)[0]['token']
survey_user_input = SurveyUserInput.search([('survey_id', '=', wizard.survey_id.id),
('state', 'in', ['new', 'skip']), '|', ('partner_id', '=', partner_id),
('email', '=', email)], limit=1)
if survey_user_input:
return survey_user_input.token
if wizard.public != 'email_private':
return None
else:
token = uuid.uuid4().__str__()
# create response with token
survey_response_obj.create(cr, uid, {
survey_user_input = SurveyUserInput.create({
'survey_id': wizard.survey_id.id,
'deadline': wizard.date_deadline,
'date_create': datetime.now(),
'date_create': fields.Datetime.now(),
'type': 'link',
'state': 'new',
'token': token,
'partner_id': partner_id,
'email': email},
context=context)
return token
'email': email})
return survey_user_input.token
for wizard in self.browse(cr, uid, ids, context=context):
for wizard in self:
# check if __URL__ is in the text
if wizard.body.find("__URL__") < 0:
raise UserError(_("The content of the text don't contain '__URL__'. \
__URL__ is automaticaly converted into the special url of the survey."))
context = self.env.context
if not wizard.multi_email and not wizard.partner_ids and (context.get('default_partner_ids') or context.get('default_multi_email')):
wizard.multi_email = context.get('default_multi_email')
wizard.partner_ids = context.get('default_partner_ids')
@@ -181,16 +146,16 @@ class survey_mail_compose_message(osv.TransientModel):
# quick check of email list
emails_list = []
if wizard.multi_email:
emails = list(set(emails_split.split(wizard.multi_email)) - set([partner.email for partner in wizard.partner_ids]))
emails = set(emails_split.split(wizard.multi_email)) - set(wizard.partner_ids.mapped('email'))
for email in emails:
email = email.strip()
if re.search(r"^[^@]+@[^@]+$", email):
if email_validator.match(email):
emails_list.append(email)
# remove public anonymous access
partner_list = []
for partner in wizard.partner_ids:
if not anonymous_id or not partner.user_ids or anonymous_id not in [x.id for x in partner.user_ids[0].groups_id]:
if not anonymous_group or not partner.user_ids or anonymous_group not in partner.user_ids[0].groups_id:
partner_list.append({'id': partner.id, 'email': partner.email})
if not len(emails_list) and not len(partner_list):
@@ -199,10 +164,9 @@ class survey_mail_compose_message(osv.TransientModel):
raise UserError(_("Please enter at least one valid recipient."))
for email in emails_list:
partner_id = partner_obj.search(cr, uid, [('email', '=', email)], context=context)
partner_id = partner_id and partner_id[0] or None
token = create_token(wizard, partner_id, email)
create_response_and_send_mail(wizard, token, partner_id, email)
partner = Partner.search([('email', '=', email)], limit=1)
token = create_token(wizard, partner.id, email)
create_response_and_send_mail(wizard, token, partner.id, email)
for partner in partner_list:
token = create_token(wizard, partner['id'], partner['email'])
@@ -30,7 +30,7 @@
<field name="res_id" invisible="1"/>
<field name="parent_id" invisible="1"/>
<group col="2">
<field name="survey_id" readonly="context.get('default_survey_id')" on_change="onchange_survey_id(survey_id)" />
<field name="survey_id" readonly="context.get('default_survey_id')"/>
<field name="public" widget="radio" invisible="context.get('survey_resent_token')" />
</group>
<group col="1" invisible="context.get('survey_resent_token')" attrs="{'invisible':[('public','in',['email_private', 'email_public_link'])]}" class="oe_survey_email_public">
@@ -45,7 +45,7 @@
<group col="1" attrs="{'invisible':['|',('public','=',False),('public','in',['public_link'])]}">
<group col="2">
<field invisible="context.get('survey_resent_token')" name="partner_ids" widget="many2many_tags_email" placeholder="Add list of existing contacts..." context="{'force_email':True, 'show_email':True}" />
<field invisible="context.get('survey_resent_token')" name="multi_email" placeholder="Add list of email of recipients (will not converted in partner), separated by commas, semicolons or newline..." on_change="onchange_multi_email(multi_email)" />
<field invisible="context.get('survey_resent_token')" name="multi_email" placeholder="Add list of email of recipients (will not converted in partner), separated by commas, semicolons or newline..."/>
<field name="subject" placeholder="Subject..."/>
</group>
<field name="body" options="{'style-inline': true}"/>