[MERGE][REF] survey: update ACLs and refactor invite wizard
Purpose of this merge is to refactor, clarify and fix surveys access rights and rules. Indeed currently surveys access rights and rules are not very clear. This application is currently used mainly for external and public surveys. In this merge we clean access on survey models. Basically only surveys officers and managers have access to the models. Other people including employees, portal users and anonymous people users have to use the portal view and dedicated routes instead of accessing data through the backend. In this merge we also expand the access options of surveys. Surveys can be public, limited to authenticated people, limited to employees or on invitation only. Invite wizard has been redone to simplify it and better support those options. For more details about the content please have a look at each subcommits. This merge is linked to task ID 1911586 and PR #28986.
This commit is contained in:
@@ -12,9 +12,9 @@
|
||||
'depends': ['survey', 'hr_recruitment'],
|
||||
'data': [
|
||||
'security/hr_recruitment_survey_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'views/hr_job_views.xml',
|
||||
'views/hr_applicant_views.xml',
|
||||
'views/survey_survey_views.xml',
|
||||
'views/res_config_setting_views.xml',
|
||||
],
|
||||
'demo': [
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<record id="survey_recruitment_form" model="survey.survey">
|
||||
<field name="title">Recruitment Form</field>
|
||||
<field name="stage_id" ref="survey.stage_permanent"/>
|
||||
<field name="auth_required" eval="True"/>
|
||||
<field name="access_mode">token</field>
|
||||
<field name="category">hr_recruitment</field>
|
||||
<field name="users_can_go_back" eval="True"/>
|
||||
<field name="description" type="html">
|
||||
<p>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import hr_job
|
||||
from . import hr_applicant
|
||||
from . import survey_survey
|
||||
|
||||
@@ -6,7 +6,7 @@ from odoo import api, fields, models
|
||||
class Applicant(models.Model):
|
||||
_inherit = "hr.applicant"
|
||||
|
||||
survey_id = fields.Many2one('survey.survey', related='job_id.survey_id', string="Survey", readonly=False)
|
||||
survey_id = fields.Many2one('survey.survey', related='job_id.survey_id', string="Survey", readonly=True)
|
||||
response_id = fields.Many2one('survey.user_input', "Response", ondelete="set null", oldname="response")
|
||||
|
||||
@api.multi
|
||||
|
||||
@@ -8,6 +8,7 @@ class Job(models.Model):
|
||||
|
||||
survey_id = fields.Many2one(
|
||||
'survey.survey', "Interview Form",
|
||||
domain=[('category', '=', 'hr_recruitment')],
|
||||
help="Choose an interview form for this job position and you will be able to print/answer this interview from all applicants who apply for this job")
|
||||
|
||||
@api.multi
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class Survey(models.Model):
|
||||
_inherit = 'survey.survey'
|
||||
|
||||
category = fields.Selection(selection_add=[('hr_recruitment', 'Recruitment')])
|
||||
@@ -1,2 +0,0 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_survey_hr_user,survey.hr.user,survey.model_survey_survey,hr_recruitment.group_hr_recruitment_user,1,1,1,0
|
||||
|
@@ -6,9 +6,10 @@
|
||||
<field name="inherit_id" ref="hr_recruitment.hr_job_survey"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='address_id']" position="before">
|
||||
<label for="survey_id" groups="base.group_user"/>
|
||||
<div groups="base.group_user" class="o_row">
|
||||
<field name="survey_id"/>
|
||||
<label for="survey_id" groups="survey.group_survey_user"/>
|
||||
<div groups="survey.group_survey_user" class="o_row">
|
||||
<field name="survey_id"
|
||||
context="{'default_category': 'hr_recruitment', 'default_access_mode': 'token'}"/>
|
||||
<button string="Display Interview Form" name="action_print_survey" type="object" attrs="{'invisible':[('survey_id','=',False)]}" class="oe_link"/>
|
||||
</div>
|
||||
</xpath>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo><data>
|
||||
<record id="survey_survey_view_form" model="ir.ui.view">
|
||||
<field name="name">survey.survey.view.form.inherit.hr_recruitment</field>
|
||||
<field name="model">survey.survey</field>
|
||||
<field name="inherit_id" ref="survey.survey_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='category']" position="attributes">
|
||||
<attribute name="invisible">0</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data></odoo>
|
||||
@@ -16,7 +16,11 @@ sent mails with personal token for the invitation of the survey.
|
||||
""",
|
||||
'summary': 'Create surveys and analyze answers',
|
||||
'website': 'https://www.odoo.com/page/survey',
|
||||
'depends': ['http_routing', 'mail', 'web_tour'],
|
||||
'depends': [
|
||||
'auth_signup',
|
||||
'http_routing',
|
||||
'mail',
|
||||
'web_tour'],
|
||||
'data': [
|
||||
'data/mail_template_data.xml',
|
||||
'data/survey_data.xml',
|
||||
|
||||
+215
-153
@@ -8,6 +8,7 @@ from datetime import datetime
|
||||
from math import ceil
|
||||
|
||||
from odoo import fields, http, SUPERUSER_ID
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.http import request
|
||||
from odoo.tools import ustr
|
||||
|
||||
@@ -16,153 +17,209 @@ _logger = logging.getLogger(__name__)
|
||||
|
||||
class Survey(http.Controller):
|
||||
|
||||
def _check_bad_cases(self, survey, token=None):
|
||||
# In case of bad survey, redirect to surveys list
|
||||
if not survey.sudo().exists():
|
||||
return werkzeug.utils.redirect("/survey/")
|
||||
def _fetch_from_access_token(self, survey_id, access_token):
|
||||
""" Check that given token matches an answer from the given survey_id.
|
||||
Returns a sudo-ed browse record of survey in order to avoid access rights
|
||||
issues now that access is granted through token. """
|
||||
survey_sudo = request.env['survey.survey'].with_context(active_test=False).sudo().browse(survey_id)
|
||||
if not access_token:
|
||||
answer_sudo = request.env['survey.user_input'].sudo()
|
||||
else:
|
||||
answer_sudo = request.env['survey.user_input'].sudo().search([
|
||||
('survey_id', '=', survey_sudo.id),
|
||||
('token', '=', access_token)
|
||||
], limit=1)
|
||||
return survey_sudo, answer_sudo
|
||||
|
||||
# In case of auth required, block public user
|
||||
if survey.auth_required and request.env.user._is_public():
|
||||
return request.render("survey.auth_required", {'survey': survey, 'token': token})
|
||||
def _check_validity(self, survey_id, access_token, ensure_token=True):
|
||||
""" Check survey is open and can be taken. This does not checks for
|
||||
security rules, only functional / business rules. It returns a string key
|
||||
allowing further manipulation of validity issues
|
||||
|
||||
# In case of non open surveys
|
||||
if survey.stage_id.closed:
|
||||
return request.render("survey.notopen")
|
||||
* survey_wrong: survey does not exist;
|
||||
* survey_auth: authentication is required;
|
||||
* survey_closed: survey is closed and does not accept input anymore;
|
||||
* survey_void: survey is void and should not be taken;
|
||||
* token_wrong: given token not recognized;
|
||||
* token_required: no token given although it is necessary to access the
|
||||
survey;
|
||||
* answer_deadline: token linked to an expired answer;
|
||||
|
||||
# If there is no pages
|
||||
if not survey.page_ids:
|
||||
return request.render("survey.nopages", {'survey': survey})
|
||||
:param ensure_token: whether user input existence based on given access token
|
||||
should be enforced or not, depending on the route requesting a token or
|
||||
allowing external world calls;
|
||||
"""
|
||||
survey_sudo, answer_sudo = self._fetch_from_access_token(survey_id, access_token)
|
||||
|
||||
# Everything seems to be ok
|
||||
return None
|
||||
if not survey_sudo.exists():
|
||||
return 'survey_wrong'
|
||||
|
||||
def _check_deadline(self, user_input):
|
||||
'''Prevent opening of the survey if the deadline has turned out
|
||||
if access_token and not answer_sudo:
|
||||
return 'token_wrong'
|
||||
|
||||
! This will NOT disallow access to users who have already partially filled the survey !'''
|
||||
deadline = user_input.deadline
|
||||
if deadline:
|
||||
dt_deadline = fields.Datetime.from_string(deadline)
|
||||
if not answer_sudo and ensure_token:
|
||||
return 'token_required'
|
||||
if not answer_sudo and survey_sudo.access_mode == 'token':
|
||||
return 'token_required'
|
||||
|
||||
# Public -> no auth required; Token -> token check hereabove
|
||||
if survey_sudo.access_mode not in ['public', 'token'] and request.env.user._is_public():
|
||||
return 'survey_auth'
|
||||
|
||||
if survey_sudo.is_closed or not survey_sudo.active:
|
||||
return 'survey_closed'
|
||||
|
||||
if not survey_sudo.page_ids:
|
||||
return 'survey_void'
|
||||
|
||||
# In case of delayed deadline # TDE FIXME
|
||||
if answer_sudo and answer_sudo.deadline:
|
||||
dt_now = datetime.now()
|
||||
if dt_now > dt_deadline: # survey is not open anymore
|
||||
return request.render("survey.notopen")
|
||||
return None
|
||||
if dt_now > answer_sudo.deadline:
|
||||
return 'answer_deadline'
|
||||
|
||||
@http.route('/survey/test/<model("survey.survey"):survey>', type='http', auth='user', website=True)
|
||||
def survey_test(self, survey, token=None, **kwargs):
|
||||
return True
|
||||
|
||||
def _get_access_data(self, survey_id, access_token, ensure_token=True):
|
||||
""" Get back data related to survey and user input, given the ID and access
|
||||
token provided by the route.
|
||||
|
||||
: param ensure_token: whether user input existence should be enforced or not(see ``_check_validity``)
|
||||
"""
|
||||
survey_sudo, answer_sudo = request.env['survey.survey'].sudo(), request.env['survey.user_input'].sudo()
|
||||
has_survey_access, can_answer = False, False
|
||||
|
||||
validity_code = self._check_validity(survey_id, access_token, ensure_token=ensure_token)
|
||||
if validity_code != 'survey_wrong':
|
||||
survey_sudo, answer_sudo = self._fetch_from_access_token(survey_id, access_token)
|
||||
try:
|
||||
survey_user = survey_sudo.sudo(request.env.user)
|
||||
survey_user.check_access_rights(self, 'read', raise_exception=True)
|
||||
survey_user.check_access_rule(self, 'read')
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
has_survey_access = True
|
||||
can_answer = bool(answer_sudo)
|
||||
if not can_answer:
|
||||
can_answer = survey_sudo.access_mode == 'public'
|
||||
|
||||
return {
|
||||
'survey_sudo': survey_sudo,
|
||||
'answer_sudo': answer_sudo,
|
||||
'has_survey_access': has_survey_access,
|
||||
'can_answer': can_answer,
|
||||
'validity_code': validity_code,
|
||||
}
|
||||
|
||||
def _redirect_with_error(self, access_data, error_key):
|
||||
survey_sudo = access_data['survey_sudo']
|
||||
answer_sudo = access_data['answer_sudo']
|
||||
|
||||
if error_key == 'survey_void' and access_data['can_answer']:
|
||||
return request.render("survey.survey_void", {'survey': survey_sudo})
|
||||
elif error_key == 'survey_closed' and access_data['can_answer']:
|
||||
return request.render("survey.survey_expired", {'survey': survey_sudo})
|
||||
elif error_key == 'survey_auth' and answer_sudo.token:
|
||||
return request.render("survey.auth_required", {'survey': survey_sudo, 'token': answer_sudo.token})
|
||||
elif error_key == 'answer_deadline' and answer_sudo.token:
|
||||
return request.render("survey.survey_expired", {'survey': survey_sudo})
|
||||
|
||||
return werkzeug.utils.redirect("/")
|
||||
|
||||
@http.route('/survey/test/<int:survey_id>', type='http', auth='user', website=True)
|
||||
def survey_test(self, survey_id, token=None, **kwargs):
|
||||
""" Test mode for surveys: create a test answer, only for managers or officers
|
||||
testing their surveys """
|
||||
if request.env.user.has_group('survey.group_survey_manager') or \
|
||||
request.env.user.has_group('survey.group_survey_user') and survey.create_uid == request.env.user:
|
||||
user_input = request.env['survey.user_input'].create({
|
||||
'survey_id': survey.id,
|
||||
'test_entry': True
|
||||
})
|
||||
return request.redirect('/survey/start/%s/%s' % (survey.id, user_input.token))
|
||||
return werkzeug.utils.redirect('/')
|
||||
survey_sudo = request.env['survey.survey'].sudo().browse(survey_id)
|
||||
try:
|
||||
answer_sudo = survey_sudo._create_answer(user=request.env.user, test_entry=True)
|
||||
except:
|
||||
return werkzeug.utils.redirect('/')
|
||||
return request.redirect('/survey/start/%s?token=%s' % (survey_sudo.id, answer_sudo.token))
|
||||
|
||||
@http.route(['/survey/start/<model("survey.survey"):survey>',
|
||||
'/survey/start/<model("survey.survey"):survey>/<string:token>'],
|
||||
type='http', auth='public', website=True)
|
||||
def survey_start(self, survey, token=None, **post):
|
||||
@http.route('/survey/start/<int:survey_id>', type='http', auth='public', website=True)
|
||||
def survey_start(self, survey_id, token=None, email=False):
|
||||
""" Start a survey by providing a token linked to an answer or generate
|
||||
a new token if access is allowed """
|
||||
UserInput = request.env['survey.user_input']
|
||||
access_data = self._get_access_data(survey_id, token, ensure_token=False)
|
||||
if access_data['validity_code'] is not True:
|
||||
return self._redirect_with_error(access_data, access_data['validity_code'])
|
||||
|
||||
# Controls if the survey can be displayed
|
||||
errpage = self._check_bad_cases(survey, token=token)
|
||||
if errpage:
|
||||
return errpage
|
||||
survey_sudo, answer_sudo = access_data['survey_sudo'], access_data['answer_sudo']
|
||||
if not answer_sudo:
|
||||
try:
|
||||
answer_sudo = survey_sudo._create_answer(user=request.env.user, email=email)
|
||||
except UserError:
|
||||
answer_sudo = False
|
||||
|
||||
# Manual surveying
|
||||
if not token:
|
||||
vals = {'survey_id': survey.id}
|
||||
if not request.env.user._is_public():
|
||||
vals['partner_id'] = request.env.user.partner_id.id
|
||||
user_input = UserInput.create(vals)
|
||||
else:
|
||||
user_input = UserInput.sudo().search([('token', '=', token)], limit=1)
|
||||
if not user_input:
|
||||
return request.render("survey.403", {'survey': survey})
|
||||
|
||||
# Do not open expired survey
|
||||
errpage = self._check_deadline(user_input)
|
||||
if errpage:
|
||||
return errpage
|
||||
if not answer_sudo:
|
||||
try:
|
||||
survey_sudo.sudo(request.env.user).check_access_rights('read')
|
||||
survey_sudo.sudo(request.env.user).check_access_rule('read')
|
||||
except:
|
||||
return werkzeug.utils.redirect("/")
|
||||
else:
|
||||
return request.render("survey.403", {'survey': survey_sudo})
|
||||
|
||||
# Select the right page
|
||||
if user_input.state == 'new': # Intro page
|
||||
data = {'survey': survey, 'page': None, 'token': user_input.token}
|
||||
if answer_sudo.state == 'new': # Intro page
|
||||
data = {'survey': survey_sudo, 'page': None, 'token': answer_sudo.token}
|
||||
return request.render('survey.survey_init', data)
|
||||
else:
|
||||
return request.redirect('/survey/fill/%s/%s' % (survey.id, user_input.token))
|
||||
return request.redirect('/survey/fill/%s/%s' % (survey_sudo.id, answer_sudo.token))
|
||||
|
||||
# Survey displaying
|
||||
@http.route(['/survey/fill/<model("survey.survey"):survey>/<string:token>',
|
||||
'/survey/fill/<model("survey.survey"):survey>/<string:token>/<string:prev>'],
|
||||
type='http', auth='public', website=True)
|
||||
def fill_survey(self, survey, token, prev=None, **post):
|
||||
'''Display and validates a survey'''
|
||||
Survey = request.env['survey.survey']
|
||||
UserInput = request.env['survey.user_input']
|
||||
@http.route('/survey/fill/<int:survey_id>/<string:token>', type='http', auth='public', website=True)
|
||||
def survey_display_page(self, survey_id, token, prev=None):
|
||||
access_data = self._get_access_data(survey_id, token, ensure_token=True)
|
||||
if access_data['validity_code'] is not True:
|
||||
return self._redirect_with_error(access_data, access_data['validity_code'])
|
||||
|
||||
# Controls if the survey can be displayed
|
||||
errpage = self._check_bad_cases(survey)
|
||||
if errpage:
|
||||
return errpage
|
||||
|
||||
# Load the user_input
|
||||
user_input = UserInput.sudo().search([('token', '=', token)], limit=1)
|
||||
if not user_input: # Invalid token
|
||||
return request.render("survey.403", {'survey': survey})
|
||||
|
||||
# Do not display expired survey (even if some pages have already been
|
||||
# displayed -- There's a time for everything!)
|
||||
errpage = self._check_deadline(user_input)
|
||||
if errpage:
|
||||
return errpage
|
||||
survey_sudo, answer_sudo = access_data['survey_sudo'], access_data['answer_sudo']
|
||||
|
||||
# Select the right page
|
||||
if user_input.state == 'new': # First page
|
||||
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 answer_sudo.state == 'new': # First page
|
||||
page, page_nr, last = survey_sudo.next_page(answer_sudo, 0, go_back=False)
|
||||
data = {'survey': survey_sudo, 'page': page, 'page_nr': page_nr, 'token': answer_sudo.token}
|
||||
if last:
|
||||
data.update({'last': True})
|
||||
return request.render('survey.survey', data)
|
||||
elif user_input.state == 'done': # Display success message
|
||||
return request.render('survey.sfinished', {'survey': survey,
|
||||
'token': token,
|
||||
'user_input': user_input})
|
||||
elif user_input.state == 'skip':
|
||||
elif answer_sudo.state == 'done': # Display success message
|
||||
return request.render('survey.sfinished', {'survey': survey_sudo,
|
||||
'token': token,
|
||||
'user_input': answer_sudo})
|
||||
elif answer_sudo.state == 'skip':
|
||||
flag = (True if prev and prev == 'prev' else False)
|
||||
page, page_nr, last = Survey.next_page(user_input, user_input.last_displayed_page_id.id, go_back=flag)
|
||||
page, page_nr, last = survey_sudo.next_page(answer_sudo, answer_sudo.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.next_page(user_input, user_input.last_displayed_page_id.id, go_back=True)
|
||||
page, page_nr, last = survey_sudo.next_page(answer_sudo, answer_sudo.last_displayed_page_id.id, go_back=True)
|
||||
|
||||
data = {'survey': survey, 'page': page, 'page_nr': page_nr, 'token': user_input.token}
|
||||
data = {'survey': survey_sudo, 'page': page, 'page_nr': page_nr, 'token': answer_sudo.token}
|
||||
if last:
|
||||
data.update({'last': True})
|
||||
return request.render('survey.survey', data)
|
||||
else:
|
||||
return request.render("survey.403", {'survey': survey})
|
||||
return request.render("survey.403", {'survey': survey_sudo})
|
||||
|
||||
# AJAX prefilling of a survey
|
||||
@http.route(['/survey/prefill/<model("survey.survey"):survey>/<string:token>',
|
||||
'/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):
|
||||
UserInputLine = request.env['survey.user_input_line']
|
||||
ret = {}
|
||||
@http.route('/survey/prefill/<int:survey_id>/<string:token>', type='http', auth='public', website=True)
|
||||
def survey_get_answers(self, survey_id, token, page_id=None):
|
||||
""" TDE NOTE: original comment: # AJAX prefilling of a survey -> AJAX / http ?? """
|
||||
access_data = self._get_access_data(survey_id, token, ensure_token=True)
|
||||
if access_data['validity_code'] is not True:
|
||||
return {}
|
||||
|
||||
survey_sudo, answer_sudo = access_data['survey_sudo'], access_data['answer_sudo']
|
||||
|
||||
# Fetch previous answers
|
||||
if page:
|
||||
previous_answers = UserInputLine.sudo().search([('user_input_id.token', '=', token), ('page_id', '=', page.id)])
|
||||
if page_id:
|
||||
previous_answers = answer_sudo.user_input_line_ids.filtered(lambda line: line.page_id.id == page_id)
|
||||
else:
|
||||
previous_answers = UserInputLine.sudo().search([('user_input_id.token', '=', token)])
|
||||
previous_answers = answer_sudo.user_input_line_ids
|
||||
|
||||
# Return non empty answers in a JSON compatible format
|
||||
ret = {}
|
||||
for answer in previous_answers:
|
||||
if not answer.skipped:
|
||||
answer_tag = '%s_%s_%s' % (answer.survey_id.id, answer.page_id.id, answer.question_id.id)
|
||||
@@ -190,32 +247,41 @@ class Survey(http.Controller):
|
||||
_logger.warning("[survey] No answer has been found for question %s marked as non skipped" % answer_tag)
|
||||
return json.dumps(ret, default=str)
|
||||
|
||||
# AJAX scores loading for quiz correction mode
|
||||
@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):
|
||||
ret = {}
|
||||
@http.route('/survey/scores/<int:survey_id>/<string:token>', type='http', auth='public', website=True)
|
||||
def survey_get_scores(self, survey_id, token, page_id=None):
|
||||
""" TDE NOTE: original comment: # AJAX scores loading for quiz correction mode -> AJAX / http ?? """
|
||||
access_data = self._get_access_data(survey_id, token, ensure_token=True)
|
||||
if access_data['validity_code'] is not True:
|
||||
return {}
|
||||
|
||||
# Fetch answers
|
||||
previous_answers = request.env['survey.user_input_line'].sudo().search([('user_input_id.token', '=', token)])
|
||||
survey_sudo, answer_sudo = access_data['survey_sudo'], access_data['answer_sudo']
|
||||
|
||||
# Compute score for each question
|
||||
for answer in previous_answers:
|
||||
ret = {}
|
||||
for answer in answer_sudo.user_input_line_ids:
|
||||
tmp_score = ret.get(answer.question_id.id, 0.0)
|
||||
ret.update({answer.question_id.id: tmp_score + answer.quizz_mark})
|
||||
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)
|
||||
def submit(self, survey, **post):
|
||||
_logger.debug('Incoming data: %s', post)
|
||||
@http.route('/survey/submit/<int:survey_id>/<string:token>', type='http', methods=['POST'], auth='public', website=True)
|
||||
def survey_submit(self, survey_id, token, **post):
|
||||
""" TDE NOTE: original comment: # AJAX submission of a page -> AJAX / http ?? """
|
||||
access_data = self._get_access_data(survey_id, token, ensure_token=True)
|
||||
if access_data['validity_code'] is not True:
|
||||
return {}
|
||||
|
||||
survey_sudo, answer_sudo = access_data['survey_sudo'], access_data['answer_sudo']
|
||||
|
||||
page_id = int(post['page_id'])
|
||||
questions = request.env['survey.question'].search([('page_id', '=', page_id)])
|
||||
questions = request.env['survey.question'].sudo().search([
|
||||
('survey_id', '=', survey_id),
|
||||
('page_id', '=', page_id)
|
||||
])
|
||||
|
||||
# Answer validation
|
||||
errors = {}
|
||||
for question in questions:
|
||||
answer_tag = "%s_%s_%s" % (survey.id, page_id, question.id)
|
||||
answer_tag = "%s_%s_%s" % (survey_id, page_id, question.id)
|
||||
errors.update(question.validate_question(post, answer_tag))
|
||||
|
||||
ret = {}
|
||||
@@ -223,46 +289,42 @@ class Survey(http.Controller):
|
||||
# Return errors messages to webpage
|
||||
ret['errors'] = errors
|
||||
else:
|
||||
# Store answers into database
|
||||
try:
|
||||
user_input = request.env['survey.user_input'].sudo().search([('token', '=', post['token'])], limit=1)
|
||||
except KeyError: # Invalid token
|
||||
return request.render("survey.403", {'survey': survey})
|
||||
user_id = request.env.user.id if user_input.input_type != 'link' else SUPERUSER_ID
|
||||
|
||||
for question in questions:
|
||||
answer_tag = "%s_%s_%s" % (survey.id, page_id, question.id)
|
||||
request.env['survey.user_input_line'].sudo(user=user_id).save_lines(user_input.id, question, post, answer_tag)
|
||||
answer_tag = "%s_%s_%s" % (survey_id, page_id, question.id)
|
||||
request.env['survey.user_input_line'].sudo().save_lines(answer_sudo.id, question, post, answer_tag)
|
||||
|
||||
go_back = post['button_submit'] == 'previous'
|
||||
next_page, _, last = request.env['survey.survey'].next_page(user_input, page_id, go_back=go_back)
|
||||
next_page, _, last = request.env['survey.survey'].next_page(answer_sudo, 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.sudo(user=user_id).write(vals)
|
||||
ret['redirect'] = '/survey/fill/%s/%s' % (survey.id, post['token'])
|
||||
answer_sudo.write(vals)
|
||||
ret['redirect'] = '/survey/fill/%s/%s' % (survey_sudo.id, token)
|
||||
if go_back:
|
||||
ret['redirect'] += '/prev'
|
||||
ret['redirect'] += '?prev=prev'
|
||||
return json.dumps(ret)
|
||||
|
||||
# Printing routes
|
||||
@http.route(['/survey/print/<model("survey.survey"):survey>',
|
||||
'/survey/print/<model("survey.survey"):survey>/<string:token>'],
|
||||
type='http', auth='public', website=True)
|
||||
def print_survey(self, survey, token=None, **post):
|
||||
@http.route('/survey/print/<int:survey_id>', type='http', auth='public', website=True)
|
||||
def survey_print(self, survey_id, token=None, **post):
|
||||
'''Display an survey in printable view; if <token> is set, it will
|
||||
grab the answers of the user_input_id that has <token>.'''
|
||||
return request.render('survey.survey_print',
|
||||
{'survey': survey,
|
||||
'token': token,
|
||||
'page_nr': 0,
|
||||
'quizz_correction': True if survey.quizz_mode and token else False})
|
||||
access_data = self._get_access_data(survey_id, token, ensure_token=False)
|
||||
if access_data['validity_code'] is not True and (
|
||||
not access_data['has_survey_access'] or access_data['validity_code'] not in ['token_required', 'survey_closed', 'survey_void']):
|
||||
return self._redirect_with_error(access_data, access_data['validity_code'])
|
||||
|
||||
@http.route(['/survey/results/<model("survey.survey"):survey>'],
|
||||
type='http', auth='user', website=True)
|
||||
def survey_reporting(self, survey, token=None, **post):
|
||||
survey_sudo, answer_sudo = access_data['survey_sudo'], access_data['answer_sudo']
|
||||
|
||||
return request.render('survey.survey_print', {
|
||||
'survey': survey_sudo,
|
||||
'token': token,
|
||||
'page_nr': 0,
|
||||
'quizz_correction': True if survey_sudo.quizz_mode and token else False})
|
||||
|
||||
@http.route('/survey/results/<model("survey.survey"):survey>', type='http', auth='user', website=True)
|
||||
def survey_report(self, survey, token=None, **post):
|
||||
'''Display survey Results & Statistics for given survey.'''
|
||||
result_template = 'survey.result'
|
||||
current_filters = []
|
||||
@@ -275,12 +337,12 @@ class Survey(http.Controller):
|
||||
post.pop('finished')
|
||||
filter_finish = True
|
||||
if post or filter_finish:
|
||||
filter_data = self.get_filter_data(post)
|
||||
filter_data = self._get_filter_data(post)
|
||||
current_filters = survey.filter_input_ids(filter_data, filter_finish)
|
||||
filter_display_data = survey.get_filter_display_data(filter_data)
|
||||
return request.render(result_template,
|
||||
{'survey': survey,
|
||||
'survey_dict': self.prepare_result_dict(survey, current_filters),
|
||||
'survey_dict': self._prepare_result_dict(survey, current_filters),
|
||||
'page_range': self.page_range,
|
||||
'current_filters': current_filters,
|
||||
'filter_display_data': filter_display_data,
|
||||
@@ -324,7 +386,7 @@ class Survey(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 = request.env['survey.survey']
|
||||
@@ -336,14 +398,14 @@ class Survey(http.Controller):
|
||||
'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),
|
||||
'graph_data': self._get_graph_data(question, current_filters),
|
||||
}
|
||||
|
||||
page_dict['question_ids'].append(question_dict)
|
||||
result['page_ids'].append(page_dict)
|
||||
return result
|
||||
|
||||
def get_filter_data(self, post):
|
||||
def _get_filter_data(self, post):
|
||||
"""Returns data used for filtering the result"""
|
||||
filters = []
|
||||
for ids in post:
|
||||
@@ -360,9 +422,9 @@ class Survey(http.Controller):
|
||||
total = ceil(total_record / float(limit))
|
||||
return range(1, int(total + 1))
|
||||
|
||||
def get_graph_data(self, question, current_filters=None):
|
||||
def _get_graph_data(self, question, current_filters=None):
|
||||
'''Returns formatted data required by graph library on basis of filter'''
|
||||
# TODO refactor this terrible method and merge it with prepare_result_dict
|
||||
# TODO refactor this terrible method and merge it with _prepare_result_dict
|
||||
current_filters = current_filters if current_filters else []
|
||||
Survey = request.env['survey.survey']
|
||||
result = []
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="email_template_survey" model="mail.template">
|
||||
<record id="mail_template_user_input_invite" model="mail.template">
|
||||
<field name="name">Survey: Send by email</field>
|
||||
<field name="model_id" ref="model_survey_survey" />
|
||||
<field name="subject">${object.title}: Survey</field>
|
||||
<field name="model_id" ref="model_survey_user_input" />
|
||||
<field name="subject">Participate to ${object.survey_id.title} survey</field>
|
||||
<field name="body_html" type="html">
|
||||
<div style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||
Hello<br /><br />
|
||||
We are conducting a survey, and your response would be appreciated.
|
||||
Dear ${object.partner_id.name or 'participant'}<br/><br/>
|
||||
We are conducting a survey and your response would be appreciated.
|
||||
<div style="margin: 16px 0px 16px 0px;">
|
||||
<a href="__URL__"
|
||||
<a href="${('%s?token=%s' % (object.survey_id.public_url, object.token)) | safe}"
|
||||
style="background-color: #875A7B; padding: 8px 16px 8px 16px; text-decoration: none; color: #fff; border-radius: 5px; font-size:13px;">
|
||||
Start Survey
|
||||
</a>
|
||||
</div>
|
||||
Thanks for your participation!
|
||||
Please answer the appraisal for ${format_date(object.deadline)}.<br/><br/>
|
||||
Thank you for your participation.
|
||||
</p>
|
||||
</div>
|
||||
</field>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<record model="survey.survey" id="survey_feedback">
|
||||
<field name="title">User Feedback Form</field>
|
||||
<field name="stage_id" ref="survey.stage_in_progress" />
|
||||
<field name="auth_required" eval="False" />
|
||||
<field name="access_mode">public</field>
|
||||
<field name="users_can_go_back" eval="True" />
|
||||
<field name="description" type="html">
|
||||
<p>This survey allows you to give a feedback about your experience with our eCommerce solution.
|
||||
@@ -229,4 +229,4 @@
|
||||
<field name="state">skip</field>
|
||||
</record>
|
||||
|
||||
</data></odoo>
|
||||
</data></odoo>
|
||||
@@ -5,21 +5,13 @@ from odoo import fields, models
|
||||
|
||||
|
||||
class SurveyPage(models.Model):
|
||||
""" A page for a survey.
|
||||
|
||||
Pages are essentially containers, allowing to group questions by ordered
|
||||
screens.
|
||||
|
||||
.. note::
|
||||
A page should be deleted if the survey it belongs to is deleted.
|
||||
"""
|
||||
""" A page for a survey. Pages are essentially containers, allowing to group questions by ordered
|
||||
screens. """
|
||||
_name = 'survey.page'
|
||||
_description = 'Survey Page'
|
||||
_rec_name = 'title'
|
||||
_order = 'sequence,id'
|
||||
|
||||
# Model Fields #
|
||||
|
||||
title = fields.Char('Page Title', required=True, translate=True)
|
||||
survey_id = fields.Many2one('survey.survey', string='Survey', ondelete='cascade', required=True)
|
||||
question_ids = fields.One2many('survey.question', 'page_id', string='Questions', copy=True)
|
||||
|
||||
@@ -20,72 +20,56 @@ def dict_keys_startswith(dictionary, string):
|
||||
|
||||
|
||||
class SurveyQuestion(models.Model):
|
||||
""" Questions that will be asked in a survey.
|
||||
|
||||
Each question can have one of more suggested answers (eg. in case of
|
||||
dropdown choices, multi-answer checkboxes, radio buttons...).
|
||||
"""
|
||||
|
||||
""" Questions that will be asked in a survey. Each question can have one of more suggested answers (eg. in case of
|
||||
dropdown choices, multi-answer checkboxes, radio buttons...). """
|
||||
_name = 'survey.question'
|
||||
_description = 'Survey Question'
|
||||
_rec_name = 'question'
|
||||
_order = 'sequence,id'
|
||||
|
||||
# Model fields #
|
||||
|
||||
# Question metadata
|
||||
page_id = fields.Many2one('survey.page', string='Survey page',
|
||||
ondelete='cascade', required=True, default=lambda self: self.env.context.get('page_id'))
|
||||
page_id = fields.Many2one(
|
||||
'survey.page', string='Survey page',
|
||||
ondelete='cascade', required=True, default=lambda self: self.env.context.get('page_id'))
|
||||
survey_id = fields.Many2one('survey.survey', related='page_id.survey_id', string='Survey', readonly=False)
|
||||
sequence = fields.Integer('Sequence', default=10)
|
||||
|
||||
# Question
|
||||
question = fields.Char('Question Name', required=True, translate=True)
|
||||
description = fields.Html('Description', help="Use this field to add \
|
||||
additional explanations about your question", translate=True,
|
||||
oldname='descriptive_text')
|
||||
|
||||
# Answer
|
||||
description = fields.Html('Description', help="Use this field to add additional explanations about your question", translate=True)
|
||||
question_type = fields.Selection([
|
||||
('free_text', 'Multiple Lines Text Box'),
|
||||
('textbox', 'Single Line Text Box'),
|
||||
('numerical_box', 'Numerical Value'),
|
||||
('date', 'Date'),
|
||||
('simple_choice', 'Multiple choice: only one answer'),
|
||||
('multiple_choice', 'Multiple choice: multiple answers allowed'),
|
||||
('matrix', 'Matrix')], string='Type of Question', default='free_text', required=True, oldname='type')
|
||||
matrix_subtype = fields.Selection([('simple', 'One choice per row'),
|
||||
('free_text', 'Multiple Lines Text Box'),
|
||||
('textbox', 'Single Line Text Box'),
|
||||
('numerical_box', 'Numerical Value'),
|
||||
('date', 'Date'),
|
||||
('simple_choice', 'Multiple choice: only one answer'),
|
||||
('multiple_choice', 'Multiple choice: multiple answers allowed'),
|
||||
('matrix', 'Matrix')], string='Type of Question',
|
||||
default='free_text', required=True, oldname='type')
|
||||
# simple choice / multiple choice / matrix
|
||||
labels_ids = fields.One2many(
|
||||
'survey.label', 'question_id', string='Types of answers', copy=True,
|
||||
help='Labels used for proposed choices: simple choice, multiple choice and columns of matrix')
|
||||
# matrix
|
||||
matrix_subtype = fields.Selection([
|
||||
('simple', 'One choice per row'),
|
||||
('multiple', 'Multiple choices per row')], string='Matrix Type', default='simple')
|
||||
labels_ids = fields.One2many('survey.label', 'question_id', string='Types of answers', oldname='answer_choice_ids', copy=True)
|
||||
labels_ids_2 = fields.One2many('survey.label', 'question_id_2', string='Rows of the Matrix', copy=True)
|
||||
# labels are used for proposed choices
|
||||
# if question.type == simple choice | multiple choice
|
||||
# -> only labels_ids is used
|
||||
# if question.type == matrix
|
||||
# -> labels_ids are the columns of the matrix
|
||||
# -> labels_ids_2 are the rows of the matrix
|
||||
|
||||
labels_ids_2 = fields.One2many(
|
||||
'survey.label', 'question_id_2', string='Rows of the Matrix', copy=True,
|
||||
help='Labels used for proposed choices: rows of matrix')
|
||||
# Display options
|
||||
column_nb = fields.Selection([('12', '1'),
|
||||
('6', '2'),
|
||||
('4', '3'),
|
||||
('3', '4'),
|
||||
('2', '6')],
|
||||
'Number of columns', default='12')
|
||||
# These options refer to col-xx-[12|6|4|3|2] classes in Bootstrap
|
||||
display_mode = fields.Selection([('columns', 'Radio Buttons'),
|
||||
('dropdown', 'Selection Box')],
|
||||
default='columns')
|
||||
|
||||
column_nb = fields.Selection([
|
||||
('12', '1'), ('6', '2'), ('4', '3'), ('3', '4'), ('2', '6')],
|
||||
string='Number of columns', default='12',
|
||||
help='These options refer to col-xx-[12|6|4|3|2] classes in Bootstrap for dropdown-based simple and multiple choice questions.')
|
||||
display_mode = fields.Selection(
|
||||
[('columns', 'Radio Buttons'), ('dropdown', 'Selection Box')],
|
||||
string='Display Mode', default='columns', help='Display mode of simple choice questions.')
|
||||
# Comments
|
||||
comments_allowed = fields.Boolean('Show Comments Field',
|
||||
oldname="allow_comment")
|
||||
comments_allowed = fields.Boolean('Show Comments Field')
|
||||
comments_message = fields.Char('Comment Message', translate=True, default=lambda self: _("If other, please specify:"))
|
||||
comment_count_as_answer = fields.Boolean('Comment Field is an Answer Choice',
|
||||
oldname='make_comment_field')
|
||||
|
||||
comment_count_as_answer = fields.Boolean('Comment Field is an Answer Choice')
|
||||
# Validation
|
||||
validation_required = fields.Boolean('Validate entry', oldname='is_validation_require')
|
||||
validation_required = fields.Boolean('Validate entry')
|
||||
validation_email = fields.Boolean('Input must be an email')
|
||||
validation_length_min = fields.Integer('Minimum Text Length')
|
||||
validation_length_max = fields.Integer('Maximum Text Length')
|
||||
@@ -93,13 +77,14 @@ class SurveyQuestion(models.Model):
|
||||
validation_max_float_value = fields.Float('Maximum value')
|
||||
validation_min_date = fields.Date('Minimum Date')
|
||||
validation_max_date = fields.Date('Maximum Date')
|
||||
validation_error_msg = fields.Char('Validation Error message', oldname='validation_valid_err_msg',
|
||||
translate=True, default=lambda self: _("The answer you entered has an invalid format."))
|
||||
|
||||
validation_error_msg = fields.Char('Validation Error message', translate=True, default=lambda self: _("The answer you entered has an invalid format."))
|
||||
# Constraints on number of answers (matrices)
|
||||
constr_mandatory = fields.Boolean('Mandatory Answer', oldname="is_require_answer")
|
||||
constr_error_msg = fields.Char('Error message', oldname='req_error_msg', translate=True, default=lambda self: _("This question requires an answer."))
|
||||
user_input_line_ids = fields.One2many('survey.user_input_line', 'question_id', string='Answers', domain=[('skipped', '=', False)])
|
||||
constr_mandatory = fields.Boolean('Mandatory Answer')
|
||||
constr_error_msg = fields.Char('Error message', translate=True, default=lambda self: _("This question requires an answer."))
|
||||
# Answer
|
||||
user_input_line_ids = fields.One2many(
|
||||
'survey.user_input_line', 'question_id', string='Answers',
|
||||
domain=[('skipped', '=', False)], groups='survey.group_survey_user')
|
||||
|
||||
_sql_constraints = [
|
||||
('positive_len_min', 'CHECK (validation_length_min >= 0)', 'A length must be positive!'),
|
||||
@@ -277,9 +262,9 @@ class SurveyQuestion(models.Model):
|
||||
errors.update({answer_tag: self.constr_error_msg})
|
||||
return errors
|
||||
|
||||
|
||||
class SurveyLabel(models.Model):
|
||||
""" A suggested answer for a question """
|
||||
|
||||
_name = 'survey.label'
|
||||
_rec_name = 'value'
|
||||
_order = 'sequence,id'
|
||||
|
||||
@@ -5,8 +5,6 @@ from odoo import fields, models
|
||||
|
||||
|
||||
class SurveyStage(models.Model):
|
||||
"""Stages for Kanban view of surveys"""
|
||||
|
||||
_name = 'survey.stage'
|
||||
_description = 'Survey Stage'
|
||||
_order = 'sequence,id'
|
||||
|
||||
@@ -11,11 +11,8 @@ from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class Survey(models.Model):
|
||||
""" Settings for a multi-page/multi-question survey.
|
||||
Each survey can have one or more attached pages, and each page can display
|
||||
one or more questions.
|
||||
"""
|
||||
|
||||
""" Settings for a multi-page/multi-question survey. Each survey can have one or more attached pages
|
||||
and each page can display one or more questions. """
|
||||
_name = 'survey.survey'
|
||||
_description = 'Survey'
|
||||
_rec_name = 'title'
|
||||
@@ -24,36 +21,43 @@ class Survey(models.Model):
|
||||
def _default_stage(self):
|
||||
return self.env['survey.stage'].search([], limit=1).id
|
||||
|
||||
# description
|
||||
title = fields.Char('Title', required=True, translate=True)
|
||||
page_ids = fields.One2many('survey.page', 'survey_id', string='Pages', copy=True)
|
||||
stage_id = fields.Many2one('survey.stage', string="Stage", default=_default_stage,
|
||||
ondelete="restrict", copy=False, group_expand='_read_group_stage_ids')
|
||||
auth_required = fields.Boolean('Login required', help="Users with a public link will be requested to login before taking part to the survey",
|
||||
oldname="authenticate")
|
||||
users_can_go_back = fields.Boolean('Users can go back', help="If checked, users can go back to previous pages.")
|
||||
tot_sent_survey = fields.Integer("Number of sent surveys", compute="_compute_survey_statistic")
|
||||
tot_start_survey = fields.Integer("Number of started surveys", compute="_compute_survey_statistic")
|
||||
tot_comp_survey = fields.Integer("Number of completed surveys", compute="_compute_survey_statistic")
|
||||
description = fields.Html("Description", translate=True, help="A long description of the purpose of the survey")
|
||||
color = fields.Integer('Color Index', default=0)
|
||||
user_input_ids = fields.One2many('survey.user_input', 'survey_id', string='User responses', readonly=True)
|
||||
designed = fields.Boolean("Is designed?", compute="_is_designed")
|
||||
public_url = fields.Char("Public link", compute="_compute_survey_url")
|
||||
public_url_html = fields.Char("Public link (html version)", compute="_compute_survey_url")
|
||||
print_url = fields.Char("Print link", compute="_compute_survey_url")
|
||||
result_url = fields.Char("Results link", compute="_compute_survey_url")
|
||||
email_template_id = fields.Many2one('mail.template', string='Email Template', ondelete='set null')
|
||||
thank_you_message = fields.Html("Thanks Message", translate=True, help="This message will be displayed when survey is completed")
|
||||
quizz_mode = fields.Boolean("Quizz Mode")
|
||||
active = fields.Boolean("Active", default=True)
|
||||
is_closed = fields.Boolean("Is closed", related='stage_id.closed', readonly=False)
|
||||
stage_id = fields.Many2one('survey.stage', string="Stage", default=_default_stage,
|
||||
ondelete="restrict", copy=False, group_expand='_read_group_stage_ids')
|
||||
is_closed = fields.Boolean("Is closed", related='stage_id.closed', readonly=True)
|
||||
category = fields.Selection([
|
||||
('default', 'Generic Survey')], string='Category',
|
||||
default='default', required=True,
|
||||
help='Category is used to know in which context the survey is used. Various apps may define their own categories when they use survey like jobs recruitment or employee appraisal surveys.')
|
||||
# content
|
||||
page_ids = fields.One2many('survey.page', 'survey_id', string='Pages', copy=True)
|
||||
user_input_ids = fields.One2many('survey.user_input', 'survey_id', string='User responses', readonly=True, groups='survey.group_survey_user')
|
||||
# security / access
|
||||
access_mode = fields.Selection([
|
||||
('public', 'Everyone'),
|
||||
('authentication', 'Login Required'),
|
||||
('internal', 'Employees Only'),
|
||||
('token', 'Invitation only')], string='Access Mode',
|
||||
default='authentication', required=True)
|
||||
users_can_go_back = fields.Boolean('Users can go back', help="If checked, users can go back to previous pages.")
|
||||
users_can_signup = fields.Boolean('Users can signup', compute='_compute_users_can_signup')
|
||||
public_url = fields.Char("Public link", compute="_compute_survey_url")
|
||||
# statistics
|
||||
tot_sent_survey = fields.Integer("Number of sent surveys", compute="_compute_survey_statistic")
|
||||
tot_start_survey = fields.Integer("Number of started surveys", compute="_compute_survey_statistic")
|
||||
tot_comp_survey = fields.Integer("Number of completed surveys", compute="_compute_survey_statistic")
|
||||
|
||||
def _is_designed(self):
|
||||
@api.multi
|
||||
def _compute_users_can_signup(self):
|
||||
signup_allowed = self.env['res.users'].sudo()._get_signup_invitation_scope() == 'b2c'
|
||||
for survey in self:
|
||||
if not survey.page_ids or not [page.question_ids for page in survey.page_ids if page.question_ids]:
|
||||
survey.designed = False
|
||||
else:
|
||||
survey.designed = True
|
||||
survey.users_can_signup = signup_allowed
|
||||
|
||||
@api.multi
|
||||
def _compute_survey_statistic(self):
|
||||
@@ -70,13 +74,9 @@ class Survey(models.Model):
|
||||
|
||||
def _compute_survey_url(self):
|
||||
""" Computes a public URL for the survey """
|
||||
base_url = '/' if self.env.context.get('relative_url') else \
|
||||
self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
||||
for survey in self:
|
||||
survey.public_url = urls.url_join(base_url, "survey/start/%s" % (slug(survey)))
|
||||
survey.print_url = urls.url_join(base_url, "survey/print/%s" % (slug(survey)))
|
||||
survey.result_url = urls.url_join(base_url, "survey/results/%s" % (slug(survey)))
|
||||
survey.public_url_html = '<a href="%s">%s</a>' % (survey.public_url, _("Click here to start survey"))
|
||||
survey.public_url = urls.url_join(base_url, "survey/start/%s" % (survey.id))
|
||||
|
||||
@api.model
|
||||
def _read_group_stage_ids(self, stages, domain, order):
|
||||
@@ -92,6 +92,65 @@ class Survey(models.Model):
|
||||
default = dict(default or {}, title=title)
|
||||
return super(Survey, self).copy_data(default)
|
||||
|
||||
@api.multi
|
||||
def _create_answer(self, user=False, partner=False, email=False, test_entry=False, **additional_vals):
|
||||
""" Main entry point to get a token back or create a new one. This method
|
||||
does check for current user access in order to explicitely validate
|
||||
security.
|
||||
|
||||
:param user: target user asking for a token; it might be void or a
|
||||
public user in which case an email is welcomed;
|
||||
:param email: email of the person asking the token is no user exists;
|
||||
"""
|
||||
self.check_access_rights('read')
|
||||
self.check_access_rule('read')
|
||||
|
||||
tokens = self.env['survey.user_input']
|
||||
for survey in self:
|
||||
if partner and not user and partner.user_ids:
|
||||
user = partner.user_ids[0]
|
||||
|
||||
survey._check_answer_creation(user, partner, email, test_entry=test_entry)
|
||||
answer_vals = {
|
||||
'survey_id': survey.id,
|
||||
'test_entry': test_entry,
|
||||
}
|
||||
if user and not user._is_public:
|
||||
answer_vals['partner_id'] = user.partner_id.id
|
||||
answer_vals['email'] = user.email
|
||||
elif partner:
|
||||
answer_vals['partner_id'] = partner.id
|
||||
answer_vals['email'] = partner.email
|
||||
else:
|
||||
answer_vals['email'] = email
|
||||
|
||||
answer_vals.update(additional_vals)
|
||||
tokens += tokens.create(answer_vals)
|
||||
|
||||
return tokens
|
||||
|
||||
@api.multi
|
||||
def _check_answer_creation(self, user, partner, email, test_entry=False):
|
||||
""" Ensure conditions to create new tokens are met. """
|
||||
self.ensure_one()
|
||||
if test_entry:
|
||||
if not user.has_group('survey.group_survey_manager') or not user.has_group('survey.group_survey_user'):
|
||||
raise UserError(_('Creating test token is not allowed for you.'))
|
||||
else:
|
||||
if not self.active:
|
||||
raise UserError(_('Creating token for archived surveys is not allowed.'))
|
||||
elif self.is_closed:
|
||||
raise UserError(_('Creating token for closed surveys is not allowed.'))
|
||||
if self.access_mode == 'authentication':
|
||||
# signup possible -> should have at least a partner to create an account
|
||||
if self.users_can_signup and not user and not partner:
|
||||
raise UserError(_('Creating token for external people is not allowed for surveys requesting authentication.'))
|
||||
# no signup possible -> should be a not public user (employee or portal users)
|
||||
if not self.users_can_signup and (not user or user._is_public()):
|
||||
raise UserError(_('Creating token for external people is not allowed for surveys requesting authentication.'))
|
||||
if self.access_mode == 'internal' and (not user or not user.has_group('base.group_user')):
|
||||
raise UserError(_('Creating token for anybody else than employees is not allowed for internal surveys.'))
|
||||
|
||||
@api.model
|
||||
def next_page(self, user_input, page_id, go_back=False):
|
||||
""" The next page to display to the user, knowing that page_id is the id
|
||||
@@ -268,12 +327,12 @@ class Survey(models.Model):
|
||||
""" Open the website page with the survey form """
|
||||
self.ensure_one()
|
||||
token = self.env.context.get('survey_token')
|
||||
trail = "/%s" % token if token else ""
|
||||
trail = "?token=%s" % token if token else ""
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'name': "Start Survey",
|
||||
'target': 'self',
|
||||
'url': self.with_context(relative_url=True).public_url + trail
|
||||
'url': self.public_url + trail
|
||||
}
|
||||
|
||||
@api.multi
|
||||
@@ -286,16 +345,13 @@ class Survey(models.Model):
|
||||
if self.stage_id.closed:
|
||||
raise UserError(_("You cannot send invitations for closed surveys."))
|
||||
|
||||
template = self.env.ref('survey.email_template_survey', raise_if_not_found=False)
|
||||
template = self.env.ref('survey.mail_template_user_input_invite', raise_if_not_found=False)
|
||||
|
||||
local_context = dict(
|
||||
self.env.context,
|
||||
default_model='survey.survey',
|
||||
default_res_id=self.id,
|
||||
default_survey_id=self.id,
|
||||
default_use_template=bool(template),
|
||||
default_template_id=template and template.id or False,
|
||||
default_composition_mode='comment',
|
||||
notif_layout='mail.mail_notification_light',
|
||||
)
|
||||
return {
|
||||
@@ -312,12 +368,12 @@ class Survey(models.Model):
|
||||
""" Open the website page with the survey printable view """
|
||||
self.ensure_one()
|
||||
token = self.env.context.get('survey_token')
|
||||
trail = "/" + token if token else ""
|
||||
trail = "?token=%s" % token if token else ""
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'name': "Print Survey",
|
||||
'target': 'self',
|
||||
'url': self.with_context(relative_url=True).print_url + trail
|
||||
'url': '/survey/print/%s%s' % (self.id, trail)
|
||||
}
|
||||
|
||||
@api.multi
|
||||
@@ -328,7 +384,7 @@ class Survey(models.Model):
|
||||
'type': 'ir.actions.act_url',
|
||||
'name': "Results of the Survey",
|
||||
'target': 'self',
|
||||
'url': self.with_context(relative_url=True).result_url
|
||||
'url': '/survey/results/%s' % self.id
|
||||
}
|
||||
|
||||
@api.multi
|
||||
|
||||
@@ -25,33 +25,28 @@ class SurveyUserInput(models.Model):
|
||||
""" Metadata for a set of one user's answers to a particular survey """
|
||||
|
||||
_name = "survey.user_input"
|
||||
_rec_name = 'date_create'
|
||||
_rec_name = 'survey_id'
|
||||
_description = 'Survey User Input'
|
||||
|
||||
survey_id = fields.Many2one('survey.survey', string='Survey', required=True, readonly=True, ondelete='restrict')
|
||||
date_create = fields.Datetime('Creation Date', default=fields.Datetime.now, required=True, readonly=True, copy=False)
|
||||
deadline = fields.Datetime('Deadline', help="Date by which the person can open the survey and submit answers", oldname="date_deadline")
|
||||
input_type = fields.Selection([('manually', 'Manually'), ('link', 'Link')], string='Answer Type', default='manually', required=True, readonly=True, oldname="type")
|
||||
# description
|
||||
survey_id = fields.Many2one('survey.survey', string='Survey', required=True, readonly=True, ondelete='cascade')
|
||||
input_type = fields.Selection([
|
||||
('manually', 'Manually'), ('link', 'Link')],
|
||||
string='Answer Type', default='manually', required=True, readonly=True,
|
||||
oldname="type")
|
||||
state = fields.Selection([
|
||||
('new', 'Not started yet'),
|
||||
('skip', 'Partially completed'),
|
||||
('done', 'Completed')], string='Status', default='new', readonly=True)
|
||||
test_entry = fields.Boolean(readonly=True)
|
||||
# identification and access
|
||||
token = fields.Char('Identification token', default=lambda self: str(uuid.uuid4()), readonly=True, required=True, copy=False)
|
||||
|
||||
# Optional Identification data
|
||||
partner_id = fields.Many2one('res.partner', string='Partner', readonly=True)
|
||||
email = fields.Char('E-mail', readonly=True)
|
||||
|
||||
# Displaying data
|
||||
last_displayed_page_id = fields.Many2one('survey.page', string='Last displayed page')
|
||||
# The answers !
|
||||
# answers
|
||||
user_input_line_ids = fields.One2many('survey.user_input_line', 'user_input_id', string='Answers', copy=True)
|
||||
|
||||
# URLs used to display the answers
|
||||
result_url = fields.Char("Public link to the survey results", related='survey_id.result_url', readonly=False)
|
||||
print_url = fields.Char("Public link to the empty survey", related='survey_id.print_url', readonly=False)
|
||||
|
||||
deadline = fields.Datetime('Deadline', help="Datetime until customer can open the survey and submit answers")
|
||||
last_displayed_page_id = fields.Many2one('survey.page', string='Last displayed page')
|
||||
quizz_score = fields.Float("Score for the quiz", compute="_compute_quizz_score", default=0.0)
|
||||
|
||||
@api.depends('user_input_line_ids.quizz_mark')
|
||||
@@ -69,54 +64,47 @@ class SurveyUserInput(models.Model):
|
||||
(used as a cronjob declared in data/survey_cron.xml)
|
||||
"""
|
||||
an_hour_ago = fields.Datetime.to_string(datetime.datetime.now() - datetime.timedelta(hours=1))
|
||||
self.search([('input_type', '=', 'manually'), ('state', '=', 'new'),
|
||||
('date_create', '<', an_hour_ago)]).unlink()
|
||||
self.search([('input_type', '=', 'manually'),
|
||||
('state', '=', 'new'),
|
||||
('create_date', '<', an_hour_ago)]).unlink()
|
||||
|
||||
@api.multi
|
||||
def action_survey_resend(self):
|
||||
""" Send again the invitation """
|
||||
self.ensure_one()
|
||||
local_context = {
|
||||
'survey_resent_token': True,
|
||||
'default_partner_ids': self.partner_id and [self.partner_id.id] or [],
|
||||
'default_multi_email': self.email or "",
|
||||
'default_public': 'email_private',
|
||||
}
|
||||
return self.survey_id.with_context(local_context).action_send_survey()
|
||||
def action_resend(self):
|
||||
partners = self.env['res.partner']
|
||||
emails = []
|
||||
for user_answer in self:
|
||||
if user_answer.partner_id:
|
||||
partners |= user_answer.partner_id
|
||||
elif user_answer.email:
|
||||
emails.append(user_answer.email)
|
||||
|
||||
return self.survey_id.with_context(
|
||||
default_existing_mode='resend',
|
||||
default_partner_ids=partners.ids,
|
||||
default_emails=','.join(emails)
|
||||
).action_send_survey()
|
||||
|
||||
@api.multi
|
||||
def action_view_answers(self):
|
||||
def action_print_answers(self):
|
||||
""" Open the website page with the survey form """
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'name': "View Answers",
|
||||
'target': 'self',
|
||||
'url': '%s/%s' % (self.print_url, self.token)
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def action_survey_results(self):
|
||||
""" Open the website page with the survey results """
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'name': "Survey Results",
|
||||
'target': 'self',
|
||||
'url': self.result_url
|
||||
'url': '/survey/print/%s?token=%s' % (self.survey_id.id, self.token)
|
||||
}
|
||||
|
||||
|
||||
class SurveyUserInputLine(models.Model):
|
||||
_name = 'survey.user_input_line'
|
||||
_description = 'Survey User Input Line'
|
||||
_rec_name = 'date_create'
|
||||
_rec_name = 'user_input_id'
|
||||
|
||||
user_input_id = fields.Many2one('survey.user_input', string='User Input', ondelete='cascade', required=True)
|
||||
question_id = fields.Many2one('survey.question', string='Question', ondelete='restrict', required=True)
|
||||
question_id = fields.Many2one('survey.question', string='Question', ondelete='cascade', required=True)
|
||||
page_id = fields.Many2one(related='question_id.page_id', string="Page", readonly=False)
|
||||
survey_id = fields.Many2one(related='user_input_id.survey_id', string='Survey', store=True, readonly=False)
|
||||
date_create = fields.Datetime('Create Date', default=fields.Datetime.now, required=True)
|
||||
skipped = fields.Boolean('Skipped')
|
||||
answer_type = fields.Selection([
|
||||
('text', 'Text'),
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_survey_public,survey.survey public,model_survey_survey,,1,0,0,0
|
||||
access_survey_page_public,survey.page public,model_survey_page,,1,0,0,0
|
||||
access_survey_question_public,survey.question public,model_survey_question,,1,0,0,0
|
||||
access_survey_label_public,survey.label public,model_survey_label,,1,0,0,0
|
||||
access_survey_user_input_public,survey.user_input public,model_survey_user_input,,1,1,1,0
|
||||
access_survey_user_input_line_public,survey.user_input_line public,model_survey_user_input_line,,1,1,1,0
|
||||
access_survey_stage_public,survey.stage public,model_survey_stage,,1,0,0,0
|
||||
access_survey_user,survey.survey user,model_survey_survey,group_survey_user,1,0,0,0
|
||||
access_survey_page_user,survey.page user,model_survey_page,group_survey_user,1,0,0,0
|
||||
access_survey_question_user,survey.question user,model_survey_question,group_survey_user,1,0,0,0
|
||||
access_survey_label_user,survey.label user,model_survey_label,group_survey_user,1,0,0,0
|
||||
access_survey_user_input_user,survey.user_input user,model_survey_user_input,group_survey_user,1,1,1,0
|
||||
access_survey_user_input_line_user,survey.user_input_line user,model_survey_user_input_line,group_survey_user,1,1,1,0
|
||||
access_survey_stage_user,survey.stage user,model_survey_stage,group_survey_user,1,0,0,0
|
||||
access_survey_manager,survey.survey manager,model_survey_survey,group_survey_manager,1,1,1,1
|
||||
access_survey_page_manager,survey.page manager,model_survey_page,group_survey_manager,1,1,1,1
|
||||
access_survey_question_manager,survey.question manager,model_survey_question,group_survey_manager,1,1,1,1
|
||||
access_survey_label_manager,survey.label manager,model_survey_label,group_survey_manager,1,1,1,1
|
||||
access_survey_user_input_manager,survey.user_input manager,model_survey_user_input,group_survey_manager,1,1,1,1
|
||||
access_survey_user_input_line_manager,survey.user_input_line manager,model_survey_user_input_line,group_survey_manager,1,1,1,1
|
||||
access_survey_stage_manager,survey.stage manager,model_survey_stage,group_survey_manager,1,1,1,1
|
||||
access_survey_all,survey.survey.all,model_survey_survey,,0,0,0,0
|
||||
access_survey_user,survey.survey.user,model_survey_survey,base.group_user,0,0,0,0
|
||||
access_survey_survey_user,survey.survey.survey.user,model_survey_survey,group_survey_user,1,1,1,1
|
||||
access_survey_survey_manager,survey.survey.survey.manager,model_survey_survey,group_survey_manager,1,1,1,1
|
||||
access_survey_stage_all,survey.stage.all,model_survey_stage,,0,0,0,0
|
||||
access_survey_stage_user,survey.stage.user,model_survey_stage,base.group_user,0,0,0,0
|
||||
access_survey_stage_survey_user,survey.stage.survey.user,model_survey_stage,group_survey_user,1,0,0,0
|
||||
access_survey_stage_survey_manager,survey.stage.survey.manager,model_survey_stage,group_survey_manager,1,1,1,1
|
||||
access_survey_page_all,survey.page.all,model_survey_page,,0,0,0,0
|
||||
access_survey_page_user,survey.page.user,model_survey_page,base.group_user,0,0,0,0
|
||||
access_survey_page_survey_user,survey.page.survey.user,model_survey_page,group_survey_user,1,1,1,1
|
||||
access_survey_page_survey_manager,survey.page.survey.manager,model_survey_page,group_survey_manager,1,1,1,1
|
||||
access_survey_question_all,survey.question.all,model_survey_question,,0,0,0,0
|
||||
access_survey_question_user,survey.question.user,model_survey_question,base.group_user,0,0,0,0
|
||||
access_survey_question_survey_user,survey.question.survey.user,model_survey_question,group_survey_user,1,1,1,1
|
||||
access_survey_question_survey_manager,survey.question.survey.manager,model_survey_question,group_survey_manager,1,1,1,1
|
||||
access_survey_label_all,survey.label.all,model_survey_label,,0,0,0,0
|
||||
access_survey_label_user,survey.label.user,model_survey_label,base.group_user,0,0,0,0
|
||||
access_survey_label_survey_user,survey.label.survey.user,model_survey_label,group_survey_user,1,1,1,1
|
||||
access_survey_label_survey_manager,survey.label.survey.manager,model_survey_label,group_survey_manager,1,1,1,1
|
||||
access_survey_user_input_all,survey.user_input.all,model_survey_user_input,,0,0,0,0
|
||||
access_survey_user_input_user,survey.user_input.user,model_survey_user_input,base.group_user,0,0,0,0
|
||||
access_survey_user_input_survey_user,survey.user_input.survey.user,model_survey_user_input,group_survey_user,1,1,1,1
|
||||
access_survey_user_input_survey_manager,survey.user_input.survey.manager,model_survey_user_input,group_survey_manager,1,1,1,1
|
||||
access_survey_user_input_line_all,survey.user_input_line.all,model_survey_user_input_line,,0,0,0,0
|
||||
access_survey_user_input_line_user,survey.user_input_line.user,model_survey_user_input_line,base.group_user,0,0,0,0
|
||||
access_survey_user_input_line_survey_user,survey.user_input_line.survey.user,model_survey_user_input_line,group_survey_user,1,1,1,1
|
||||
access_survey_user_input_line_survey_manager,survey.user_input_line.survey.manager,model_survey_user_input_line,group_survey_manager,1,1,1,1
|
||||
|
||||
|
@@ -19,61 +19,213 @@
|
||||
<field name="groups_id" eval="[(4,ref('group_survey_manager'))]"/>
|
||||
</record>
|
||||
|
||||
<!-- Record rules -->
|
||||
<record id="survey_users_access" model="ir.rule">
|
||||
<field name="name">Access to survey for regular users</field>
|
||||
<field name="model_id" ref="survey.model_survey_survey"/>
|
||||
<field name="domain_force">[('stage_id.closed', '=', False)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_survey_user'))]"/>
|
||||
<field eval="0" name="perm_unlink"/>
|
||||
<field eval="0" name="perm_write"/>
|
||||
<field eval="1" name="perm_read"/>
|
||||
<field eval="0" name="perm_create"/>
|
||||
</record>
|
||||
|
||||
<record id="survey_manager_access" model="ir.rule">
|
||||
<field name="name">Survey Manager access rights</field>
|
||||
<!-- SURVEY: SURVEY, PAGE, STAGE, QUESTION, LABEL -->
|
||||
<record id="survey_survey_rule_survey_manager" model="ir.rule">
|
||||
<field name="name">Survey: manager: all</field>
|
||||
<field name="model_id" ref="survey.model_survey_survey"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_survey_manager'))]"/>
|
||||
<field eval="1" name="perm_unlink"/>
|
||||
<field eval="1" name="perm_write"/>
|
||||
<field eval="1" name="perm_read"/>
|
||||
<field eval="1" name="perm_create"/>
|
||||
<field name="perm_unlink" eval="1"/>
|
||||
<field name="perm_write" eval="1"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_create" eval="1"/>
|
||||
</record>
|
||||
|
||||
<record id="survey_input_public_access" model="ir.rule">
|
||||
<field name="name">Public access to user_input</field>
|
||||
<field name="model_id" ref="survey.model_survey_user_input"/>
|
||||
<record id="survey_survey_rule_survey_user_read" model="ir.rule">
|
||||
<field name="name">Survey: officer: read all</field>
|
||||
<field name="model_id" ref="survey.model_survey_survey"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_survey_user'))]"/>
|
||||
<field name="perm_unlink" eval="0"/>
|
||||
<field name="perm_write" eval="0"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_create" eval="0"/>
|
||||
</record>
|
||||
<record id="survey_survey_rule_survey_user_cwu" model="ir.rule">
|
||||
<field name="name">Survey: officer: create/write/unlink own only</field>
|
||||
<field name="model_id" ref="survey.model_survey_survey"/>
|
||||
<field name="domain_force">[('create_uid', '=', user.id)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_user')), (4, ref('base.group_portal')), (4, ref('base.group_public'))]"/>
|
||||
<field eval="0" name="perm_unlink"/>
|
||||
<field eval="0" name="perm_write"/>
|
||||
<field eval="1" name="perm_read"/>
|
||||
<field eval="0" name="perm_create"/>
|
||||
</record>
|
||||
|
||||
<record id="survey_input_users_access" model="ir.rule">
|
||||
<field name="name">Access to user_input for regular users</field>
|
||||
<field name="model_id" ref="survey.model_survey_user_input"/>
|
||||
<field name="domain_force">['|', ('create_uid', '=', user.id), ('partner_id', '=', user.partner_id.id)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_survey_user'))]"/>
|
||||
<field eval="0" name="perm_unlink"/>
|
||||
<field eval="0" name="perm_write"/>
|
||||
<field eval="1" name="perm_read"/>
|
||||
<field eval="0" name="perm_create"/>
|
||||
<field name="perm_unlink" eval="1"/>
|
||||
<field name="perm_write" eval="1"/>
|
||||
<field name="perm_read" eval="0"/>
|
||||
<field name="perm_create" eval="1"/>
|
||||
</record>
|
||||
|
||||
<record id="survey_input_manager_access" model="ir.rule">
|
||||
<field name="name">Survey Manager access rights</field>
|
||||
<record id="survey_page_rule_survey_manager" model="ir.rule">
|
||||
<field name="name">Survey page: manager: all</field>
|
||||
<field name="model_id" ref="survey.model_survey_page"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_survey_manager'))]"/>
|
||||
<field name="perm_unlink" eval="1"/>
|
||||
<field name="perm_write" eval="1"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_create" eval="1"/>
|
||||
</record>
|
||||
<record id="survey_page_rule_survey_user_read" model="ir.rule">
|
||||
<field name="name">Survey page: officer: read all</field>
|
||||
<field name="model_id" ref="survey.model_survey_page"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_survey_user'))]"/>
|
||||
<field name="perm_unlink" eval="0"/>
|
||||
<field name="perm_write" eval="0"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_create" eval="0"/>
|
||||
</record>
|
||||
<record id="survey_page_rule_survey_user_cwu" model="ir.rule">
|
||||
<field name="name">Survey page: officer: create/write/unlink linked to own survey only</field>
|
||||
<field name="model_id" ref="survey.model_survey_page"/>
|
||||
<field name="domain_force">[('survey_id.create_uid', '=', user.id)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_survey_user'))]"/>
|
||||
<field name="perm_unlink" eval="1"/>
|
||||
<field name="perm_write" eval="1"/>
|
||||
<field name="perm_read" eval="0"/>
|
||||
<field name="perm_create" eval="1"/>
|
||||
</record>
|
||||
|
||||
<record id="survey_stage_rule_survey_manager" model="ir.rule">
|
||||
<field name="name">Survey stage: manager: all</field>
|
||||
<field name="model_id" ref="survey.model_survey_stage"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_survey_manager'))]"/>
|
||||
<field name="perm_unlink" eval="1"/>
|
||||
<field name="perm_write" eval="1"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_create" eval="1"/>
|
||||
</record>
|
||||
<record id="survey_stage_rule_survey_user_read" model="ir.rule">
|
||||
<field name="name">Survey stage: officer: read all</field>
|
||||
<field name="model_id" ref="survey.model_survey_page"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_survey_user'))]"/>
|
||||
<field name="perm_unlink" eval="0"/>
|
||||
<field name="perm_write" eval="0"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_create" eval="0"/>
|
||||
</record>
|
||||
|
||||
<record id="survey_question_rule_survey_manager" model="ir.rule">
|
||||
<field name="name">Survey question: manager: all</field>
|
||||
<field name="model_id" ref="survey.model_survey_question"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_survey_manager'))]"/>
|
||||
<field name="perm_unlink" eval="1"/>
|
||||
<field name="perm_write" eval="1"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_create" eval="1"/>
|
||||
</record>
|
||||
<record id="survey_question_rule_survey_user_read" model="ir.rule">
|
||||
<field name="name">Survey question: officer: read all</field>
|
||||
<field name="model_id" ref="survey.model_survey_question"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_survey_user'))]"/>
|
||||
<field name="perm_unlink" eval="0"/>
|
||||
<field name="perm_write" eval="0"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_create" eval="0"/>
|
||||
</record>
|
||||
<record id="survey_question_rule_survey_user_cw" model="ir.rule">
|
||||
<field name="name">Survey question: officer: create/write/unlink linked to own survey only</field>
|
||||
<field name="model_id" ref="survey.model_survey_question"/>
|
||||
<field name="domain_force">[('survey_id.create_uid', '=', user.id)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_survey_user'))]"/>
|
||||
<field name="perm_unlink" eval="1"/>
|
||||
<field name="perm_write" eval="1"/>
|
||||
<field name="perm_read" eval="0"/>
|
||||
<field name="perm_create" eval="1"/>
|
||||
</record>
|
||||
|
||||
<record id="survey_label_rule_survey_manager" model="ir.rule">
|
||||
<field name="name">Survey label: manager: all</field>
|
||||
<field name="model_id" ref="survey.model_survey_label"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_survey_manager'))]"/>
|
||||
<field name="perm_unlink" eval="1"/>
|
||||
<field name="perm_write" eval="1"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_create" eval="1"/>
|
||||
</record>
|
||||
<record id="survey_label_rule_survey_user_read" model="ir.rule">
|
||||
<field name="name">Survey label: officer: read all</field>
|
||||
<field name="model_id" ref="survey.model_survey_label"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_survey_user'))]"/>
|
||||
<field name="perm_unlink" eval="0"/>
|
||||
<field name="perm_write" eval="0"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_create" eval="0"/>
|
||||
</record>
|
||||
<record id="survey_label_rule_survey_user_cw" model="ir.rule">
|
||||
<field name="name">Survey label: officer: create/write/unlink linked to own survey only</field>
|
||||
<field name="model_id" ref="survey.model_survey_question"/>
|
||||
<field name="domain_force">[('survey_id.create_uid', '=', user.id)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_survey_user'))]"/>
|
||||
<field name="perm_unlink" eval="1"/>
|
||||
<field name="perm_write" eval="1"/>
|
||||
<field name="perm_read" eval="0"/>
|
||||
<field name="perm_create" eval="1"/>
|
||||
</record>
|
||||
|
||||
<!-- SURVEY: USER_INPUT, USER_INPUT_LINE -->
|
||||
<record id="survey_user_input_rule_survey_manager" model="ir.rule">
|
||||
<field name="name">Survey user input: manager: all</field>
|
||||
<field name="model_id" ref="survey.model_survey_user_input"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_survey_manager'))]"/>
|
||||
<field eval="1" name="perm_unlink"/>
|
||||
<field eval="1" name="perm_write"/>
|
||||
<field eval="1" name="perm_read"/>
|
||||
<field eval="1" name="perm_create"/>
|
||||
<field name="perm_unlink" eval="1"/>
|
||||
<field name="perm_write" eval="1"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_create" eval="1"/>
|
||||
</record>
|
||||
<record id="survey_user_input_rule_survey_user_read" model="ir.rule">
|
||||
<field name="name">Survey user input: officer: read all</field>
|
||||
<field name="model_id" ref="survey.model_survey_user_input"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_survey_user'))]"/>
|
||||
<field name="perm_unlink" eval="0"/>
|
||||
<field name="perm_write" eval="0"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_create" eval="0"/>
|
||||
</record>
|
||||
<record id="survey_user_input_rule_survey_user_cw" model="ir.rule">
|
||||
<field name="name">Survey user input: officer: create/write/unlink linked to own survey only</field>
|
||||
<field name="model_id" ref="survey.model_survey_user_input"/>
|
||||
<field name="domain_force">[('survey_id.create_uid', '=', user.id)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_survey_user'))]"/>
|
||||
<field name="perm_unlink" eval="1"/>
|
||||
<field name="perm_write" eval="1"/>
|
||||
<field name="perm_read" eval="0"/>
|
||||
<field name="perm_create" eval="1"/>
|
||||
</record>
|
||||
|
||||
<record id="survey_user_input_line_rule_survey_manager" model="ir.rule">
|
||||
<field name="name">Survey user input line: manager: all</field>
|
||||
<field name="model_id" ref="survey.model_survey_user_input_line"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_survey_manager'))]"/>
|
||||
<field name="perm_unlink" eval="1"/>
|
||||
<field name="perm_write" eval="1"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_create" eval="1"/>
|
||||
</record>
|
||||
<record id="survey_user_input_line_rule_survey_user_read" model="ir.rule">
|
||||
<field name="name">Survey user input line: officer: read all</field>
|
||||
<field name="model_id" ref="survey.model_survey_user_input_line"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_survey_user'))]"/>
|
||||
<field name="perm_unlink" eval="0"/>
|
||||
<field name="perm_write" eval="0"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_create" eval="0"/>
|
||||
</record>
|
||||
<record id="survey_user_input_line_rule_survey_user_cw" model="ir.rule">
|
||||
<field name="name">Survey user input line: officer: create/write/unlink linked to own survey only</field>
|
||||
<field name="model_id" ref="survey.model_survey_user_input_line"/>
|
||||
<field name="domain_force">[('user_input_id.survey_id.create_uid', '=', user.id)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_survey_user'))]"/>
|
||||
<field name="perm_unlink" eval="1"/>
|
||||
<field name="perm_write" eval="1"/>
|
||||
<field name="perm_read" eval="0"/>
|
||||
<field name="perm_create" eval="1"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -5,4 +5,5 @@ from . import common
|
||||
from . import test_survey
|
||||
from . import test_survey_flow
|
||||
from . import test_survey_invite
|
||||
from . import test_survey_security
|
||||
from . import test_survey_ui
|
||||
|
||||
@@ -64,7 +64,7 @@ class SurveyCase(common.SavepointCase):
|
||||
|
||||
self.survey = self.env['survey.survey'].sudo(self.survey_manager).create({
|
||||
'title': 'Test Survey',
|
||||
'auth_required': True,
|
||||
'access_mode': 'authentication',
|
||||
'users_can_go_back': False,
|
||||
})
|
||||
self.page_0 = self.env['survey.page'].sudo(self.survey_manager).create({
|
||||
|
||||
@@ -175,19 +175,3 @@ class TestSurveyInternals(common.SurveyCase):
|
||||
result = self.env['survey.survey'].prepare_result(question)
|
||||
for key in exresult:
|
||||
self.assertEqual(result[key], exresult[key])
|
||||
|
||||
@users('survey_manager')
|
||||
def test_survey_urls(self):
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
||||
urltypes = {'public': 'start', 'print': 'print', 'result': 'results'}
|
||||
for urltype, urltxt in urltypes.items():
|
||||
survey_url = getattr(self.survey, urltype + '_url')
|
||||
survey_url_relative = getattr(self.survey.with_context({'relative_url': True}), urltype + '_url')
|
||||
url = "survey/%s/%s" % (urltxt, slug(self.survey))
|
||||
full_url = urls.url_join(base_url, url)
|
||||
self.assertEqual(full_url, survey_url)
|
||||
self.assertEqual('/' + url, survey_url_relative)
|
||||
if urltype == 'public':
|
||||
url_html = '<a href="%s">Click here to start survey</a>'
|
||||
self.assertEqual(url_html % full_url, getattr(self.survey, urltype + '_url_html'), msg="Public URL is incorrect")
|
||||
self.assertEqual(url_html % ('/' + url), getattr(self.survey.with_context({'relative_url': True}), urltype + '_url_html'))
|
||||
|
||||
@@ -17,8 +17,8 @@ class TestSurveyFlow(common.SurveyCase, HttpCase):
|
||||
def _access_page(self, survey, token):
|
||||
return self.url_open('/survey/fill/%s/%s' % (survey.id, token))
|
||||
|
||||
def _access_submit(self, survey, post_data):
|
||||
return self.url_open('/survey/submit/%s' % survey.id, data=post_data)
|
||||
def _access_submit(self, survey, token, post_data):
|
||||
return self.url_open('/survey/submit/%s/%s' % (survey.id, token), data=post_data)
|
||||
|
||||
def _find_csrf_token(self, text):
|
||||
csrf_token_re = re.compile("(input.+csrf_token.+value=\")([_a-zA-Z0-9]{51})", re.MULTILINE)
|
||||
@@ -47,7 +47,7 @@ class TestSurveyFlow(common.SurveyCase, HttpCase):
|
||||
with self.sudo(self.survey_manager):
|
||||
survey = self.env['survey.survey'].create({
|
||||
'title': 'Public Survey for Tarte Al Djotte',
|
||||
'auth_required': False,
|
||||
'access_mode': 'public',
|
||||
})
|
||||
|
||||
# First page is about customer data
|
||||
@@ -108,7 +108,7 @@ class TestSurveyFlow(common.SurveyCase, HttpCase):
|
||||
page0_q1.id: {'value': [44.0]},
|
||||
}
|
||||
post_data = self._format_submission_data(page_0, answer_data, {'csrf_token': csrf_token, 'token': answer_token, 'button_submit': 'next'})
|
||||
r = self._access_submit(survey, post_data)
|
||||
r = self._access_submit(survey, answer_token, post_data)
|
||||
self.assertResponse(r, 200)
|
||||
answers.invalidate_cache() # TDE note: necessary as lots of sudo in controllers messing with cache
|
||||
|
||||
@@ -127,7 +127,7 @@ class TestSurveyFlow(common.SurveyCase, HttpCase):
|
||||
page1_q0.id: {'value': [page1_q0.labels_ids.ids[0], page1_q0.labels_ids.ids[1]]},
|
||||
}
|
||||
post_data = self._format_submission_data(page_1, answer_data, {'csrf_token': csrf_token, 'token': answer_token, 'button_submit': 'next'})
|
||||
r = self._access_submit(survey, post_data)
|
||||
r = self._access_submit(survey, answer_token, post_data)
|
||||
self.assertResponse(r, 200)
|
||||
answers.invalidate_cache() # TDE note: necessary as lots of sudo in controllers messing with cache
|
||||
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import fields
|
||||
from odoo.addons.survey.tests import common
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import Form
|
||||
from odoo.tests.common import users
|
||||
|
||||
|
||||
class TestSurveyInvite(common.SurveyCase):
|
||||
|
||||
def setUp(self):
|
||||
res = super(TestSurveyInvite, self).setUp()
|
||||
# by default signup not allowed
|
||||
self.env["ir.config_parameter"].set_param('auth_signup.invitation_scope', 'b2b')
|
||||
return res
|
||||
|
||||
@users('survey_manager')
|
||||
def test_survey_invite_action(self):
|
||||
# Check correctly configured survey returns an invite wizard action
|
||||
@@ -30,3 +41,153 @@ class TestSurveyInvite(common.SurveyCase):
|
||||
for survey in surveys:
|
||||
with self.assertRaises(UserError):
|
||||
survey.action_send_survey()
|
||||
|
||||
@users('survey_manager')
|
||||
def test_survey_invite(self):
|
||||
Answer = self.env['survey.user_input']
|
||||
deadline = fields.Datetime.now() + relativedelta(months=1)
|
||||
|
||||
self.survey.write({'access_mode': 'public'})
|
||||
action = self.survey.action_send_survey()
|
||||
invite_form = Form(self.env[action['res_model']].with_context(action['context']))
|
||||
|
||||
# some lowlevel checks that action is correctly configured
|
||||
self.assertEqual(Answer.search([('survey_id', '=', self.survey.id)]), self.env['survey.user_input'])
|
||||
self.assertEqual(invite_form.survey_id, self.survey)
|
||||
|
||||
invite_form.partner_ids.add(self.customer)
|
||||
invite_form.deadline = fields.Datetime.to_string(deadline)
|
||||
|
||||
invite = invite_form.save()
|
||||
invite.action_invite()
|
||||
|
||||
answers = Answer.search([('survey_id', '=', self.survey.id)])
|
||||
self.assertEqual(len(answers), 1)
|
||||
self.assertEqual(
|
||||
set(answers.mapped('email')),
|
||||
set([self.customer.email]))
|
||||
self.assertEqual(answers.mapped('partner_id'), self.customer)
|
||||
self.assertEqual(set(answers.mapped('deadline')), set([deadline]))
|
||||
|
||||
@users('survey_manager')
|
||||
def test_survey_invite_authentication_nosignup(self):
|
||||
Answer = self.env['survey.user_input']
|
||||
|
||||
self.survey.write({'access_mode': 'authentication'})
|
||||
action = self.survey.action_send_survey()
|
||||
invite_form = Form(self.env[action['res_model']].with_context(action['context']))
|
||||
|
||||
with self.assertRaises(UserError): # do not allow to add customer (partner without user)
|
||||
invite_form.partner_ids.add(self.customer)
|
||||
invite_form.partner_ids.clear()
|
||||
invite_form.partner_ids.add(self.user_portal.partner_id)
|
||||
invite_form.partner_ids.add(self.user_emp.partner_id)
|
||||
with self.assertRaises(UserError):
|
||||
invite_form.emails = 'test1@example.com, Raoulette Vignolette <test2@example.com>'
|
||||
invite_form.emails = False
|
||||
|
||||
invite = invite_form.save()
|
||||
invite.action_invite()
|
||||
|
||||
answers = Answer.search([('survey_id', '=', self.survey.id)])
|
||||
self.assertEqual(len(answers), 2)
|
||||
self.assertEqual(
|
||||
set(answers.mapped('email')),
|
||||
set([self.user_emp.email, self.user_portal.email]))
|
||||
self.assertEqual(answers.mapped('partner_id'), self.user_emp.partner_id | self.user_portal.partner_id)
|
||||
|
||||
@users('survey_manager')
|
||||
def test_survey_invite_authentication_signup(self):
|
||||
self.env["ir.config_parameter"].sudo().set_param('auth_signup.invitation_scope', 'b2c')
|
||||
self.survey.invalidate_cache()
|
||||
Answer = self.env['survey.user_input']
|
||||
|
||||
self.survey.write({'access_mode': 'authentication'})
|
||||
action = self.survey.action_send_survey()
|
||||
invite_form = Form(self.env[action['res_model']].with_context(action['context']))
|
||||
|
||||
invite_form.partner_ids.add(self.customer)
|
||||
invite_form.partner_ids.add(self.user_portal.partner_id)
|
||||
invite_form.partner_ids.add(self.user_emp.partner_id)
|
||||
# TDE FIXME: not sure for emails in authentication + signup
|
||||
# invite_form.emails = 'test1@example.com, Raoulette Vignolette <test2@example.com>'
|
||||
|
||||
invite = invite_form.save()
|
||||
invite.action_invite()
|
||||
|
||||
answers = Answer.search([('survey_id', '=', self.survey.id)])
|
||||
self.assertEqual(len(answers), 3)
|
||||
self.assertEqual(
|
||||
set(answers.mapped('email')),
|
||||
set([self.customer.email, self.user_emp.email, self.user_portal.email]))
|
||||
self.assertEqual(answers.mapped('partner_id'), self.customer | self.user_emp.partner_id | self.user_portal.partner_id)
|
||||
|
||||
@users('survey_manager')
|
||||
def test_survey_invite_internal(self):
|
||||
Answer = self.env['survey.user_input']
|
||||
|
||||
self.survey.write({'access_mode': 'internal'})
|
||||
action = self.survey.action_send_survey()
|
||||
invite_form = Form(self.env[action['res_model']].with_context(action['context']))
|
||||
|
||||
with self.assertRaises(UserError): # do not allow to add customer (partner without user)
|
||||
invite_form.partner_ids.add(self.customer)
|
||||
with self.assertRaises(UserError): # do not allow to add portal user
|
||||
invite_form.partner_ids.add(self.user_portal.partner_id)
|
||||
invite_form.partner_ids.clear()
|
||||
invite_form.partner_ids.add(self.user_emp.partner_id)
|
||||
with self.assertRaises(UserError):
|
||||
invite_form.emails = 'test1@example.com, Raoulette Vignolette <test2@example.com>'
|
||||
invite_form.emails = False
|
||||
|
||||
invite = invite_form.save()
|
||||
invite.action_invite()
|
||||
|
||||
answers = Answer.search([('survey_id', '=', self.survey.id)])
|
||||
self.assertEqual(len(answers), 1)
|
||||
self.assertEqual(
|
||||
set(answers.mapped('email')),
|
||||
set([self.user_emp.email]))
|
||||
self.assertEqual(answers.mapped('partner_id'), self.user_emp.partner_id)
|
||||
|
||||
@users('survey_manager')
|
||||
def test_survey_invite_public(self):
|
||||
Answer = self.env['survey.user_input']
|
||||
|
||||
self.survey.write({'access_mode': 'public'})
|
||||
action = self.survey.action_send_survey()
|
||||
invite_form = Form(self.env[action['res_model']].with_context(action['context']))
|
||||
|
||||
invite_form.partner_ids.add(self.customer)
|
||||
invite_form.emails = 'test1@example.com, Raoulette Vignolette <test2@example.com>'
|
||||
|
||||
invite = invite_form.save()
|
||||
invite.action_invite()
|
||||
|
||||
answers = Answer.search([('survey_id', '=', self.survey.id)])
|
||||
self.assertEqual(len(answers), 3)
|
||||
self.assertEqual(
|
||||
set(answers.mapped('email')),
|
||||
set(['test1@example.com', 'Raoulette Vignolette <test2@example.com>', self.customer.email]))
|
||||
self.assertEqual(answers.mapped('partner_id'), self.customer)
|
||||
|
||||
@users('survey_manager')
|
||||
def test_survey_invite_token(self):
|
||||
Answer = self.env['survey.user_input']
|
||||
|
||||
self.survey.write({'access_mode': 'token'})
|
||||
action = self.survey.action_send_survey()
|
||||
invite_form = Form(self.env[action['res_model']].with_context(action['context']))
|
||||
|
||||
invite_form.partner_ids.add(self.customer)
|
||||
invite_form.emails = 'test1@example.com, Raoulette Vignolette <test2@example.com>'
|
||||
|
||||
invite = invite_form.save()
|
||||
invite.action_invite()
|
||||
|
||||
answers = Answer.search([('survey_id', '=', self.survey.id)])
|
||||
self.assertEqual(len(answers), 3)
|
||||
self.assertEqual(
|
||||
set(answers.mapped('email')),
|
||||
set(['test1@example.com', 'Raoulette Vignolette <test2@example.com>', self.customer.email]))
|
||||
self.assertEqual(answers.mapped('partner_id'), self.customer)
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.survey.tests import common
|
||||
from odoo.exceptions import AccessError, UserError
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import users
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('security')
|
||||
class TestAccess(common.SurveyCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestAccess, self).setUp()
|
||||
|
||||
self.answer_0 = self._add_answer(self.survey, self.customer)
|
||||
self.answer_0_0 = self._add_answer_line(self.question_ft, self.answer_0, 'Test Answer')
|
||||
self.answer_0_1 = self._add_answer_line(self.question_num, self.answer_0, 5)
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_model')
|
||||
@users('user_emp')
|
||||
def test_access_survey_employee(self):
|
||||
# Create: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.survey'].create({'title': 'Test Survey 2'})
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.page'].create({'title': 'My Page', 'survey_id': self.survey.id})
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.question'].create({'question': 'My Question', 'page_id': self.page_0.id})
|
||||
|
||||
# Read: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.survey'].search([('title', 'ilike', 'Test')])
|
||||
with self.assertRaises(AccessError):
|
||||
self.survey.sudo(self.env.user).read(['title'])
|
||||
|
||||
# Write: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.survey.sudo(self.env.user).write({'title': 'New Title'})
|
||||
with self.assertRaises(AccessError):
|
||||
self.page_0.sudo(self.env.user).write({'title': 'New Title'})
|
||||
with self.assertRaises(AccessError):
|
||||
self.question_ft.sudo(self.env.user).write({'question': 'New Title'})
|
||||
|
||||
# Unlink: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.survey.sudo(self.env.user).unlink()
|
||||
with self.assertRaises(AccessError):
|
||||
self.page_0.sudo(self.env.user).unlink()
|
||||
with self.assertRaises(AccessError):
|
||||
self.question_ft.sudo(self.env.user).unlink()
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_model')
|
||||
@users('user_portal')
|
||||
def test_access_survey_portal(self):
|
||||
# Create: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.survey'].create({'title': 'Test Survey 2'})
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.page'].create({'title': 'My Page', 'survey_id': self.survey.id})
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.question'].create({'question': 'My Question', 'page_id': self.page_0.id})
|
||||
|
||||
# Read: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.survey'].search([('title', 'ilike', 'Test')])
|
||||
with self.assertRaises(AccessError):
|
||||
self.survey.sudo(self.env.user).read(['title'])
|
||||
|
||||
# Write: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.survey.sudo(self.env.user).write({'title': 'New Title'})
|
||||
with self.assertRaises(AccessError):
|
||||
self.page_0.sudo(self.env.user).write({'title': 'New Title'})
|
||||
with self.assertRaises(AccessError):
|
||||
self.question_ft.sudo(self.env.user).write({'question': 'New Title'})
|
||||
|
||||
# Unlink: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.survey.sudo(self.env.user).unlink()
|
||||
with self.assertRaises(AccessError):
|
||||
self.page_0.sudo(self.env.user).unlink()
|
||||
with self.assertRaises(AccessError):
|
||||
self.question_ft.sudo(self.env.user).unlink()
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_model')
|
||||
@users('user_public')
|
||||
def test_access_survey_public(self):
|
||||
# Create: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.survey'].create({'title': 'Test Survey 2'})
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.page'].create({'title': 'My Page', 'survey_id': self.survey.id})
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.question'].create({'question': 'My Question', 'page_id': self.page_0.id})
|
||||
|
||||
# Read: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.survey'].search([('title', 'ilike', 'Test')])
|
||||
with self.assertRaises(AccessError):
|
||||
self.survey.sudo(self.env.user).read(['title'])
|
||||
|
||||
# Write: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.survey.sudo(self.env.user).write({'title': 'New Title'})
|
||||
with self.assertRaises(AccessError):
|
||||
self.page_0.sudo(self.env.user).write({'title': 'New Title'})
|
||||
with self.assertRaises(AccessError):
|
||||
self.question_ft.sudo(self.env.user).write({'question': 'New Title'})
|
||||
|
||||
# Unlink: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.survey.sudo(self.env.user).unlink()
|
||||
with self.assertRaises(AccessError):
|
||||
self.page_0.sudo(self.env.user).unlink()
|
||||
with self.assertRaises(AccessError):
|
||||
self.question_ft.sudo(self.env.user).unlink()
|
||||
|
||||
@users('survey_manager')
|
||||
def test_access_survey_survey_manager(self):
|
||||
# Create: all
|
||||
survey = self.env['survey.survey'].create({'title': 'Test Survey 2'})
|
||||
page = self.env['survey.page'].create({'title': 'My Page', 'survey_id': survey.id})
|
||||
self.env['survey.question'].create({'question': 'My Question', 'page_id': page.id})
|
||||
|
||||
# Read: all
|
||||
surveys = self.env['survey.survey'].search([('title', 'ilike', 'Test')])
|
||||
self.assertEqual(surveys, self.survey | survey)
|
||||
surveys.read(['title'])
|
||||
|
||||
# Write: all
|
||||
(self.survey | survey).write({'title': 'New Title'})
|
||||
|
||||
# Unlink: all
|
||||
(self.survey | survey).unlink()
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_model')
|
||||
@users('survey_user')
|
||||
def test_access_survey_survey_user(self):
|
||||
# Create: own only
|
||||
survey = self.env['survey.survey'].create({'title': 'Test Survey 2'})
|
||||
page = self.env['survey.page'].create({'title': 'My Page', 'survey_id': survey.id})
|
||||
self.env['survey.question'].create({'question': 'My Question', 'page_id': page.id})
|
||||
|
||||
# Read: all
|
||||
surveys = self.env['survey.survey'].search([('title', 'ilike', 'Test')])
|
||||
self.assertEqual(surveys, self.survey | survey)
|
||||
surveys.read(['title'])
|
||||
|
||||
# Write: own only
|
||||
survey.write({'title': 'New Title'})
|
||||
with self.assertRaises(AccessError):
|
||||
self.survey.sudo(self.env.user).write({'title': 'New Title'})
|
||||
|
||||
# Unlink: own only
|
||||
survey.unlink()
|
||||
with self.assertRaises(AccessError):
|
||||
self.survey.sudo(self.env.user).unlink()
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_model')
|
||||
@users('user_emp')
|
||||
def test_access_answers_employee(self):
|
||||
# Create: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.user_input'].create({'survey_id': self.survey.id})
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.user_input_line'].create({'question_id': self.question_num.id, 'answer_type': 'number', 'value_number': 3, 'user_input_id': self.answer_0.id})
|
||||
|
||||
# Read: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.user_input'].search([('survey_id', 'in', [self.survey.id])])
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.user_input_line'].search([('survey_id', 'in', [self.survey.id])])
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.user_input'].browse(self.answer_0.ids).read(['state'])
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.user_input_line'].browse(self.answer_0_0.ids).read(['value_number'])
|
||||
|
||||
# Write: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.answer_0.sudo(self.env.user).write({'state': 'done'})
|
||||
|
||||
# Unlink: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.answer_0.sudo(self.env.user).unlink()
|
||||
with self.assertRaises(AccessError):
|
||||
self.answer_0_0.sudo(self.env.user).unlink()
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_model')
|
||||
@users('user_portal')
|
||||
def test_access_answers_portal(self):
|
||||
# Create: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.user_input'].create({'survey_id': self.survey.id})
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.user_input_line'].create({'question_id': self.question_num.id, 'answer_type': 'number', 'value_number': 3, 'user_input_id': self.answer_0.id})
|
||||
|
||||
# Read: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.user_input'].search([('survey_id', 'in', [self.survey.id])])
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.user_input_line'].search([('survey_id', 'in', [self.survey.id])])
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.user_input'].browse(self.answer_0.ids).read(['state'])
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.user_input_line'].browse(self.answer_0_0.ids).read(['value_number'])
|
||||
|
||||
# Write: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.answer_0.sudo(self.env.user).write({'state': 'done'})
|
||||
|
||||
# Unlink: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.answer_0.sudo(self.env.user).unlink()
|
||||
with self.assertRaises(AccessError):
|
||||
self.answer_0_0.sudo(self.env.user).unlink()
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_model')
|
||||
@users('user_public')
|
||||
def test_access_answers_public(self):
|
||||
# Create: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.user_input'].create({'survey_id': self.survey.id})
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.user_input_line'].create({'question_id': self.question_num.id, 'answer_type': 'number', 'value_number': 3, 'user_input_id': self.answer_0.id})
|
||||
|
||||
# Read: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.user_input'].search([('survey_id', 'in', [self.survey.id])])
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.user_input_line'].search([('survey_id', 'in', [self.survey.id])])
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.user_input'].browse(self.answer_0.ids).read(['state'])
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['survey.user_input_line'].browse(self.answer_0_0.ids).read(['value_number'])
|
||||
|
||||
# Write: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.answer_0.sudo(self.env.user).write({'state': 'done'})
|
||||
|
||||
# Unlink: nope
|
||||
with self.assertRaises(AccessError):
|
||||
self.answer_0.sudo(self.env.user).unlink()
|
||||
with self.assertRaises(AccessError):
|
||||
self.answer_0_0.sudo(self.env.user).unlink()
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_model')
|
||||
@users('survey_user')
|
||||
def test_access_answers_survey_user(self):
|
||||
survey_own = self.env['survey.survey'].create({'title': 'Other'})
|
||||
page_own = self.env['survey.page'].create({'title': 'Other', 'survey_id': survey_own.id})
|
||||
question_own = self.env['survey.question'].create({'question': 'Other Question', 'page_id': page_own.id})
|
||||
|
||||
# Create: own survey only
|
||||
answer_own = self.env['survey.user_input'].create({'survey_id': survey_own.id})
|
||||
answer_line_own = self.env['survey.user_input_line'].create({'question_id': question_own.id, 'answer_type': 'number', 'value_number': 3, 'user_input_id': answer_own.id})
|
||||
|
||||
# Read: always
|
||||
answers = self.env['survey.user_input'].search([('survey_id', 'in', [survey_own.id, self.survey.id])])
|
||||
self.assertEqual(answers, answer_own | self.answer_0)
|
||||
|
||||
answer_lines = self.env['survey.user_input_line'].search([('survey_id', 'in', [survey_own.id, self.survey.id])])
|
||||
self.assertEqual(answer_lines, answer_line_own | self.answer_0_0 | self.answer_0_1)
|
||||
|
||||
self.env['survey.user_input'].browse(answer_own.ids).read(['state'])
|
||||
self.env['survey.user_input'].browse(self.answer_0.ids).read(['state'])
|
||||
|
||||
self.env['survey.user_input_line'].browse(answer_line_own.ids).read(['value_number'])
|
||||
self.env['survey.user_input_line'].browse(self.answer_0_0.ids).read(['value_number'])
|
||||
|
||||
# Create: own survey only (moved after read because DB not correctly rollbacked with assertRaises)
|
||||
with self.assertRaises(AccessError):
|
||||
answer_other = self.env['survey.user_input'].create({'survey_id': self.survey.id})
|
||||
with self.assertRaises(AccessError):
|
||||
answer_line_other = self.env['survey.user_input_line'].create({'question_id': self.question_num.id, 'answer_type': 'number', 'value_number': 3, 'user_input_id': self.answer_0.id})
|
||||
|
||||
# Write: own survey only
|
||||
answer_own.write({'state': 'done'})
|
||||
with self.assertRaises(AccessError):
|
||||
self.answer_0.sudo(self.env.user).write({'state': 'done'})
|
||||
|
||||
# Unlink: own survey only
|
||||
answer_own.unlink()
|
||||
answer_line_own.unlink()
|
||||
with self.assertRaises(AccessError):
|
||||
self.answer_0.sudo(self.env.user).unlink()
|
||||
with self.assertRaises(AccessError):
|
||||
self.answer_0_0.sudo(self.env.user).unlink()
|
||||
|
||||
@users('survey_manager')
|
||||
def test_access_answers_survey_manager(self):
|
||||
admin = self.env.ref('base.user_admin')
|
||||
with self.sudo(admin):
|
||||
survey_other = self.env['survey.survey'].create({'title': 'Other'})
|
||||
page_other = self.env['survey.page'].create({'title': 'Other', 'survey_id': survey_other.id})
|
||||
question_other = self.env['survey.question'].create({'question': 'Other Question', 'page_id': page_other.id})
|
||||
self.assertEqual(survey_other.create_uid, admin)
|
||||
self.assertEqual(question_other.create_uid, admin)
|
||||
|
||||
# Create: always
|
||||
answer_own = self.env['survey.user_input'].create({'survey_id': self.survey.id})
|
||||
answer_other = self.env['survey.user_input'].create({'survey_id': survey_other.id})
|
||||
answer_line_own = self.env['survey.user_input_line'].create({'question_id': self.question_num.id, 'answer_type': 'number', 'value_number': 3, 'user_input_id': answer_own.id})
|
||||
answer_line_other = self.env['survey.user_input_line'].create({'question_id': question_other.id, 'answer_type': 'number', 'value_number': 3, 'user_input_id': answer_other.id})
|
||||
|
||||
# Read: always
|
||||
answers = self.env['survey.user_input'].search([('survey_id', 'in', [survey_other.id, self.survey.id])])
|
||||
self.assertEqual(answers, answer_own | answer_other | self.answer_0)
|
||||
|
||||
answer_lines = self.env['survey.user_input_line'].search([('survey_id', 'in', [survey_other.id, self.survey.id])])
|
||||
self.assertEqual(answer_lines, answer_line_own | answer_line_other | self.answer_0_0 | self.answer_0_1)
|
||||
|
||||
self.env['survey.user_input'].browse(answer_own.ids).read(['state'])
|
||||
self.env['survey.user_input'].browse(self.answer_0.ids).read(['state'])
|
||||
|
||||
self.env['survey.user_input_line'].browse(answer_line_own.ids).read(['value_number'])
|
||||
self.env['survey.user_input_line'].browse(self.answer_0_0.ids).read(['value_number'])
|
||||
|
||||
# Write: always
|
||||
answer_own.write({'state': 'done'})
|
||||
answer_other.write({'partner_id': self.env.user.partner_id.id})
|
||||
|
||||
# Unlink: always
|
||||
(answer_own | answer_other | self.answer_0).unlink()
|
||||
@@ -6,10 +6,10 @@ import odoo.tests
|
||||
class TestUi(odoo.tests.HttpCase):
|
||||
|
||||
def test_01_admin_survey_tour(self):
|
||||
self.phantom_js("/survey/start/user-feedback-form-1", "odoo.__DEBUG__.services['web_tour.tour'].run('test_survey')", "odoo.__DEBUG__.services['web_tour.tour'].tours.test_survey.ready", login="admin")
|
||||
self.phantom_js("/survey/start/1", "odoo.__DEBUG__.services['web_tour.tour'].run('test_survey')", "odoo.__DEBUG__.services['web_tour.tour'].tours.test_survey.ready", login="admin")
|
||||
|
||||
def test_02_demo_survey_tour(self):
|
||||
self.phantom_js("/survey/start/user-feedback-form-1", "odoo.__DEBUG__.services['web_tour.tour'].run('test_survey')", "odoo.__DEBUG__.services['web_tour.tour'].tours.test_survey.ready", login="demo")
|
||||
self.phantom_js("/survey/start/1", "odoo.__DEBUG__.services['web_tour.tour'].run('test_survey')", "odoo.__DEBUG__.services['web_tour.tour'].tours.test_survey.ready", login="demo")
|
||||
|
||||
def test_03_public_survey_tour(self):
|
||||
self.phantom_js("/survey/start/user-feedback-form-1", "odoo.__DEBUG__.services['web_tour.tour'].run('test_survey')", "odoo.__DEBUG__.services['web_tour.tour'].tours.test_survey.ready")
|
||||
self.phantom_js("/survey/start/1", "odoo.__DEBUG__.services['web_tour.tour'].run('test_survey')", "odoo.__DEBUG__.services['web_tour.tour'].tours.test_survey.ready")
|
||||
|
||||
@@ -9,12 +9,11 @@
|
||||
<field name="id" invisible="1"/>
|
||||
<field name="tot_start_survey" invisible="1"/>
|
||||
<field name="is_closed" invisible="1"/>
|
||||
<header groups="survey.group_survey_manager">
|
||||
<button name="action_test_survey" string="Test Survey" type="object" attrs="{'invisible': [('id', '=', False)]}"/>
|
||||
<button name="action_print_survey" string="Print Survey" type="object" attrs="{'invisible': [('id', '=', False)]}"/>
|
||||
<button name="action_send_survey" string="Share and invite by email" type="object" class="oe_highlight" attrs="{'invisible': [('id', '=', False)]}"/>
|
||||
<button name="action_result_survey" string="View results" type="object" attrs="{'invisible': ['|',('id', '=', False), ('tot_start_survey', '!=', 0)]}"/>
|
||||
<button name="action_result_survey" string="View results" type="object" class="oe_highlight" attrs="{'invisible': [('tot_start_survey', '=', 0)]}"/>
|
||||
<header>
|
||||
<button name="action_test_survey" string="Test" type="object" attrs="{'invisible': [('id', '=', False)]}"/>
|
||||
<button name="action_print_survey" string="Print" type="object" attrs="{'invisible': [('id', '=', False)]}"/>
|
||||
<button name="action_send_survey" string="Share and invite" type="object" class="oe_highlight" attrs="{'invisible': [('id', '=', False)]}"/>
|
||||
<button name="action_result_survey" string="View results" type="object" class="oe_highlight" attrs="{'invisible': [('id', '=', False)]}"/>
|
||||
<field name="stage_id" widget="statusbar" options="{'clickable': '1'}"/>
|
||||
</header>
|
||||
<sheet>
|
||||
@@ -25,7 +24,7 @@
|
||||
icon="fa-pencil-square-o">
|
||||
<field string="Answers" name="tot_comp_survey" widget="statinfo"/>
|
||||
</button>
|
||||
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive" attrs="{'invisible': [('is_closed', '=', False)]}">
|
||||
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
</div>
|
||||
@@ -33,24 +32,25 @@
|
||||
<label for="title" class="oe_edit_only"/>
|
||||
<h1><field name="title" placeholder="Survey Title"/></h1>
|
||||
</div>
|
||||
<notebook>
|
||||
<page string="Edit Pages and Questions">
|
||||
<field name="page_ids" mode="tree" context="{'default_survey_id': active_id}" nolabel="1">
|
||||
<tree>
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="title"/>
|
||||
<field name="question_ids"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Options">
|
||||
<group class="o_label_nowrap">
|
||||
<field name="users_can_go_back" string="User can come back in the previous page"/>
|
||||
<field name="auth_required"/>
|
||||
<field name="quizz_mode" groups="base.group_no_one"/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
<group>
|
||||
<group>
|
||||
<field name="access_mode"/>
|
||||
<field name="users_can_go_back"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="category" invisible="1"/>
|
||||
<field name="quizz_mode"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Edit Pages and Questions">
|
||||
<field name="page_ids" mode="tree" context="{'default_survey_id': active_id}" nolabel="1">
|
||||
<tree>
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="title"/>
|
||||
<field name="question_ids"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
@@ -80,14 +80,12 @@
|
||||
<field name="arch" type="xml">
|
||||
<kanban default_group_by="stage_id">
|
||||
<field name="stage_id" />
|
||||
<field name="designed" />
|
||||
<field name="title" />
|
||||
<field name="tot_sent_survey" />
|
||||
<field name="tot_start_survey" />
|
||||
<field name="tot_comp_survey" />
|
||||
<field name="color" />
|
||||
<field name="auth_required" />
|
||||
<field name="public_url" />
|
||||
<field name="access_mode"/>
|
||||
<field name="activity_ids" />
|
||||
<field name="activity_state" />
|
||||
<templates>
|
||||
@@ -110,13 +108,11 @@
|
||||
</div>
|
||||
<div class="o_kanban_record_bottom">
|
||||
<ul class="list-unstyled">
|
||||
<li id="survey_test">
|
||||
<t t-if="record.designed.raw_value"><a type="object" name="action_test_survey">Test</a></t>
|
||||
<t t-if="! record.designed.raw_value"><a style="color: #aaaaaa;">Test</a></t>
|
||||
<li>
|
||||
<a type="object" name="action_test_survey">Test</a>
|
||||
</li>
|
||||
<li>
|
||||
<t t-if="record.designed.raw_value"><a type="object" name="action_send_survey">Share &amp; Invite</a></t>
|
||||
<t t-if="! record.designed.raw_value"><a style="color: #aaaaaa;">Share &amp; Invite</a></t>
|
||||
<a type="object" name="action_send_survey">Share &amp; Invite</a>
|
||||
</li>
|
||||
<li>
|
||||
<t t-if="record.tot_start_survey.raw_value > 0"><a name="action_result_survey" type="object">Analyze Answers</a> <span t-if="record.tot_start_survey.raw_value > 0">(<field name="tot_start_survey" />)</span></t>
|
||||
@@ -154,6 +150,9 @@
|
||||
<filter string="Upcoming Activities" name="activities_upcoming_all"
|
||||
domain="[('activity_ids.date_deadline', '>', context_today().strftime('%Y-%m-%d'))
|
||||
]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Category" name="groupby_category" context="{'group_by': 'category'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
@@ -169,12 +168,10 @@
|
||||
</p><p>
|
||||
You can create surveys for different purposes: customer opinion, services feedback, recruitment interviews, employee's periodical evaluations, marketing campaigns, etc.
|
||||
</p><p>
|
||||
Design easily your survey, send invitations to answer by email and analyse answers.
|
||||
Design easily your survey, send invitations to answer by email and analyze answers.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
<act_window context="{'search_default_survey_id': [active_id], 'default_survey_id': active_id}" id="act_survey_pages" name="Pages" res_model="survey.page" src_model="survey.survey"/>
|
||||
<act_window context="{'search_default_survey_id': [active_id], 'default_survey_id': active_id}" id="act_survey_question" name="Questions" res_model="survey.question" src_model="survey.survey"/>
|
||||
|
||||
<menuitem name="Surveys" id="menu_survey_form" action="action_survey_form" parent="menu_surveys" sequence="1"/>
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<h1>Thank you!</h1>
|
||||
<div t-field="survey.thank_you_message" class="oe_no_empty" />
|
||||
<div t-if='survey.quizz_mode'>You scored <t t-esc="user_input.quizz_score" /> points.</div>
|
||||
<div>If you wish, you can <a t-att-href="'/survey/print/%s/%s' % (slug(survey), token)">review your answers</a>.</div>
|
||||
<div>If you wish, you can <a t-att-href="'/survey/print/%s?token=%s' % (survey.id, token)">review your answers</a>.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -34,13 +34,13 @@
|
||||
</template>
|
||||
|
||||
<!-- Message when the survey is not open -->
|
||||
<template id="notopen" name="Survey not open">
|
||||
<template id="survey_expired" name="Survey Expired">
|
||||
<t t-call="survey.layout">
|
||||
<div class="wrap">
|
||||
<div class="container">
|
||||
<div class="jumbotron mt32">
|
||||
<h1>Not open</h1>
|
||||
<p>This survey is not open. Thank you for your interest!</p>
|
||||
<h1><span t-field="survey.title"/> survey expired</h1>
|
||||
<p>This survey is now closed. Thank you for your interest !</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -54,7 +54,9 @@
|
||||
<div class="container">
|
||||
<div class="jumbotron mt32">
|
||||
<h1>Login required</h1>
|
||||
<p>This survey is open only to registered people. Please <a t-attf-href="/web/login?redirect=%2Fsurvey%2Fstart%2F#{ slug(survey) }%2F#{token}">log in</a>.</p>
|
||||
<p>This survey is open only to registered people. Please
|
||||
<a t-att-href="'/web/login?redirect=%s' % ('/survey/start/%s?token=%s' % (survey.id, token))">log in</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -62,14 +64,18 @@
|
||||
</template>
|
||||
|
||||
<!-- Message when the survey has no pages -->
|
||||
<template id="nopages" name="Survey has no pages">
|
||||
<template id="survey_void" name="Survey has no pages">
|
||||
<t t-call="survey.layout">
|
||||
<div class="wrap">
|
||||
<div class="container">
|
||||
<t t-call="survey.back" />
|
||||
<div class="jumbotron mt32">
|
||||
<h1>Not ready</h1>
|
||||
<p>This survey has no pages by now!</p>
|
||||
<h1><span t-field="survey.title"/> survey is void</h1>
|
||||
<p>This survey has no question defined currently.<br />
|
||||
<a t-att-href="'/web#view_type=form&model=survey.survey&id=%s&action=survey.action_survey_form' % survey.id"
|
||||
class="btn btn-secondary"
|
||||
groups="survey.group_survey_manager">Edit in backend</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -135,7 +141,7 @@
|
||||
<div class='jumbotron mt32'>
|
||||
<h1 t-field='survey.title' />
|
||||
<div t-field='survey.description' class="oe_no_empty"/>
|
||||
<a role="button" class="btn btn-primary btn-lg" t-att-href="'/survey/fill/%s/%s' % (slug(survey), token)">
|
||||
<a role="button" class="btn btn-primary btn-lg" t-att-href="'/survey/fill/%s/%s' % (survey.id, token)">
|
||||
Start Survey
|
||||
</a>
|
||||
</div>
|
||||
@@ -167,7 +173,11 @@
|
||||
<div t-field='page.description' class="oe_no_empty"/>
|
||||
</div>
|
||||
|
||||
<form role="form" method="post" class="js_surveyform" t-att-name="'%s_%s' % (survey.id, page.id)" t-att-action="'/survey/fill/%s/%s' % (slug(survey), token)" t-att-data-prefill="'/survey/prefill/%s/%s/%s' % (slug(survey), token, slug(page))" t-att-data-validate="'/survey/validate/%s' % (slug(survey))" t-att-data-submit="'/survey/submit/%s' % (slug(survey))">
|
||||
<form role="form" method="post" class="js_surveyform" t-att-name="'%s_%s' % (survey.id, page.id)"
|
||||
t-att-action="'/survey/fill/%s/%s' % (survey.id, token)"
|
||||
t-att-data-prefill="'/survey/prefill/%s/%s?page_id=%s' % (survey.id, token, page.id)"
|
||||
t-att-data-validate="'/survey/validate/%s/%s' % (survey.id, token)"
|
||||
t-att-data-submit="'/survey/submit/%s/%s' % (survey.id, token)">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="page_id" t-att-value="page.id" />
|
||||
<input type="hidden" name="token" t-att-value="token" />
|
||||
@@ -336,7 +346,7 @@
|
||||
<h1><span t-field='survey.title'/></h1>
|
||||
<t t-if="survey.description"><div t-field='survey.description' class="oe_no_empty"/></t>
|
||||
</div>
|
||||
<div role="form" class="js_surveyform" t-att-name="'%s' % (survey.id)" t-att-data-prefill="'/survey/prefill/%s/%s' % (slug(survey), token)">
|
||||
<div role="form" class="js_surveyform" t-att-name="'%s' % (survey.id)" t-att-data-prefill="'/survey/prefill/%s/%s' % (survey.id, token)">
|
||||
<t t-foreach="survey.page_ids" t-as="page">
|
||||
<div class="o_page_header">
|
||||
<h1 t-field='page.title' />
|
||||
@@ -455,20 +465,20 @@
|
||||
<tbody>
|
||||
<t t-set="text_result" t-value="prepare_result"/>
|
||||
<tr t-foreach="text_result" t-as="user_input">
|
||||
<td><a t-att-href="'%s/%s' % (user_input.user_input_id.print_url, user_input.user_input_id.token)"><t t-esc="user_input_index + 1"></t></a></td>
|
||||
<td><a t-att-href="'/survey/print/%s?token=%s' % (user_input.survey_id.id, user_input.user_input_id.token)"><t t-esc="user_input_index + 1"></t></a></td>
|
||||
<t t-if="question.question_type == 'free_text'">
|
||||
<td>
|
||||
<a t-att-href="'%s/%s' % (user_input.user_input_id.print_url, user_input.user_input_id.token)" t-field="user_input.value_free_text"></a><br/>
|
||||
<a t-att-href="'/survey/print/%s?token=%s' % (user_input.survey_id.id, user_input.user_input_id.token)" t-field="user_input.value_free_text"></a><br/>
|
||||
</td>
|
||||
</t>
|
||||
<t t-if="question.question_type == 'textbox'">
|
||||
<td>
|
||||
<a t-att-href="'%s/%s' % (user_input.user_input_id.print_url, user_input.user_input_id.token)" t-field="user_input.value_text"></a><br/>
|
||||
<a t-att-href="'/survey/print/%s?token=%s' % (user_input.survey_id.id, user_input.user_input_id.token)" t-field="user_input.value_text"></a><br/>
|
||||
</td>
|
||||
</t>
|
||||
<t t-if="question.question_type == 'date'">
|
||||
<td>
|
||||
<a t-att-href="'%s/%s' % (user_input.user_input_id.print_url, user_input.user_input_id.token)" t-field="user_input.value_date"></a><br/>
|
||||
<a t-att-href="'/survey/print/%s?token=%s' % (user_input.survey_id.id, user_input.user_input_id.token)" t-field="user_input.value_date"></a><br/>
|
||||
</td>
|
||||
</t>
|
||||
</tr>
|
||||
@@ -489,7 +499,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="comments" t-as="user_input">
|
||||
<td><a t-att-href="'%s/%s' % (user_input.user_input_id.print_url, user_input.user_input_id.token)"><t t-esc="user_input_index + 1"></t></a></td>
|
||||
<td><a t-att-href="'/survey/print/%s?token=%s' % (user_input.survey_id.id, user_input.user_input_id.token)"><t t-esc="user_input_index + 1"></t></a></td>
|
||||
<td>
|
||||
<span t-field="user_input.value_text"></span><br/>
|
||||
</td>
|
||||
@@ -673,7 +683,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="d-none" t-foreach="number_result['input_lines']" t-as="user_input">
|
||||
<td><a t-att-href="'%s/%s' % (user_input.user_input_id.print_url, user_input.user_input_id.token)"><t t-esc="user_input_index + 1"></t></a></td>
|
||||
<td><a t-att-href="'/survey/print/%s?token=%s' % (user_input.survey_id.id, user_input.user_input_id.token)"><t t-esc="user_input_index + 1"></t></a></td>
|
||||
<td><span t-field="user_input.value_number"></span><br/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
<field name="arch" type="xml">
|
||||
<form string="Survey User inputs" create="false">
|
||||
<header>
|
||||
<button name="action_survey_resend" string="Send Invitation Again" type="object" class="oe_highlight" attrs="{'invisible': ['|', ('input_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" />
|
||||
<button name="action_resend" string="Resend Invitation" type="object" class="oe_highlight"
|
||||
attrs="{'invisible': ['|', ('state', '=', 'done'), '&', ('partner_id', '=', False), ('email', '=', False)]}"/>
|
||||
<button name="action_print_answers" states="done" string="Print" type="object" class="oe_highlight"/>
|
||||
<field name="state" widget="statusbar"/>
|
||||
</header>
|
||||
<sheet>
|
||||
@@ -39,7 +39,7 @@
|
||||
<group col="2">
|
||||
<group>
|
||||
<field name="survey_id"/>
|
||||
<field name="date_create"/>
|
||||
<field name="create_date"/>
|
||||
<field name="input_type"/>
|
||||
<field name="token" groups="base.group_no_one"/>
|
||||
</group>
|
||||
@@ -57,7 +57,7 @@
|
||||
<field name="question_id"/>
|
||||
<field name="answer_type"/>
|
||||
<field name="skipped"/>
|
||||
<field name="date_create"/>
|
||||
<field name="create_date"/>
|
||||
<field name="quizz_mark" groups="base.group_no_one"/>
|
||||
</tree>
|
||||
</field>
|
||||
@@ -71,7 +71,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Survey User inputs" decoration-muted="test_entry == True" create="false">
|
||||
<field name="survey_id"/>
|
||||
<field name="date_create"/>
|
||||
<field name="create_date"/>
|
||||
<field name="deadline"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="email"/>
|
||||
@@ -89,7 +89,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<kanban create="false">
|
||||
<field name="survey_id"/>
|
||||
<field name="date_create"/>
|
||||
<field name="create_date"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="email"/>
|
||||
<field name="input_type"/>
|
||||
@@ -105,7 +105,7 @@
|
||||
</div>
|
||||
<div class="o_kanban_record_bottom">
|
||||
<div class="oe_kanban_bottom_left">
|
||||
<field name="date_create"/>
|
||||
<field name="create_date"/>
|
||||
</div>
|
||||
<div class="oe_kanban_bottom_right mr4">
|
||||
<field name="state" widget="kanban_label_selection" options="{'classes': {'new': 'default', 'done': 'success', 'skip':'warning'}}"/>
|
||||
@@ -155,7 +155,7 @@
|
||||
<sheet>
|
||||
<group col="4">
|
||||
<field name="question_id"/>
|
||||
<field name="date_create"/>
|
||||
<field name="create_date"/>
|
||||
<field name="answer_type"/>
|
||||
<field name="skipped" />
|
||||
<field name="quizz_mark" groups="base.group_no_one"/>
|
||||
@@ -181,7 +181,7 @@
|
||||
<field name="user_input_id"/>
|
||||
<field name="page_id"/>
|
||||
<field name="question_id"/>
|
||||
<field name="date_create"/>
|
||||
<field name="create_date"/>
|
||||
<field name="answer_type"/>
|
||||
<field name="skipped"/>
|
||||
<field name="quizz_mark" groups="base.group_no_one"/>
|
||||
|
||||
@@ -3,29 +3,21 @@
|
||||
|
||||
import logging
|
||||
import re
|
||||
import uuid
|
||||
|
||||
from email.utils import formataddr
|
||||
from werkzeug import urls
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
emails_split = re.compile(r"[;,\n\r]+")
|
||||
email_validator = re.compile(r"[^@]+@[^@]+\.[^@]+")
|
||||
|
||||
|
||||
class SurveyInvite(models.TransientModel):
|
||||
_name = 'survey.invite'
|
||||
_description = 'Survey Invitation Wizard'
|
||||
|
||||
def default_survey_id(self):
|
||||
context = self.env.context
|
||||
if context.get('model') == 'survey.survey':
|
||||
return context.get('res_id')
|
||||
|
||||
@api.model
|
||||
def _get_default_from(self):
|
||||
if self.env.user.email:
|
||||
@@ -44,7 +36,7 @@ class SurveyInvite(models.TransientModel):
|
||||
string='Attachments')
|
||||
template_id = fields.Many2one(
|
||||
'mail.template', 'Use template', index=True,
|
||||
domain="[('model', '=', 'survey.survey')]")
|
||||
domain="[('model', '=', 'survey.user_input')]")
|
||||
# origin
|
||||
email_from = fields.Char('From', default=_get_default_from, help="Email address of the sender.")
|
||||
author_id = fields.Many2one(
|
||||
@@ -52,216 +44,194 @@ class SurveyInvite(models.TransientModel):
|
||||
ondelete='set null', default=_get_default_author,
|
||||
help="Author of the message.")
|
||||
# recipients
|
||||
partner_ids = fields.Many2many('res.partner', 'survey_mail_compose_message_res_partner_rel', 'wizard_id', 'partner_id', string='Existing contacts')
|
||||
multi_email = fields.Text(string='List of emails', help="This list of emails of recipients will not be converted in contacts.\
|
||||
partner_ids = fields.Many2many(
|
||||
'res.partner', 'survey_mail_compose_message_res_partner_rel', 'wizard_id', 'partner_id', string='Recipients')
|
||||
emails = fields.Text(string='Additional emails', help="This list of emails of recipients will not be converted in contacts.\
|
||||
Emails must be separated by commas, semicolons or newline.")
|
||||
existing_mode = fields.Selection([
|
||||
('skip', 'Skip'), ('resend', 'Resend'), ('prevent', 'Prevent')],
|
||||
string='Existing', default='skip')
|
||||
# technical info
|
||||
mail_server_id = fields.Many2one('ir.mail_server', 'Outgoing mail server')
|
||||
# survey
|
||||
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")
|
||||
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.")
|
||||
survey_id = fields.Many2one('survey.survey', string='Survey', required=True)
|
||||
survey_url = fields.Char(related="survey_id.public_url", readonly=True)
|
||||
survey_access_mode = fields.Selection(related="survey_id.access_mode", readonly=True)
|
||||
deadline = fields.Datetime(string="Answer deadline")
|
||||
|
||||
@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
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
res = super(SurveyInvite, 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['active_ids']})
|
||||
return res
|
||||
|
||||
@api.onchange('multi_email')
|
||||
def onchange_multi_email(self):
|
||||
emails = list(set(emails_split.split(self.multi_email or "")))
|
||||
emails_checked = []
|
||||
error_message = ""
|
||||
@api.onchange('emails')
|
||||
def _onchange_emails(self):
|
||||
if self.emails and (self.survey_access_mode == 'internal' or (self.survey_access_mode == 'authentication' and not self.survey_id.users_can_signup)):
|
||||
raise UserError(_('This survey does not allow external people to participate. You should create user accounts or update survey access mode accordingly.'))
|
||||
if not self.emails:
|
||||
return
|
||||
valid, error = [], []
|
||||
emails = list(set(emails_split.split(self.emails or "")))
|
||||
for email in emails:
|
||||
email = email.strip()
|
||||
if email:
|
||||
if not email_validator.match(email):
|
||||
error_message += "\n'%s'" % email
|
||||
else:
|
||||
emails_checked.append(email)
|
||||
if error_message:
|
||||
raise UserError(_("Incorrect Email Address: %s") % error_message)
|
||||
email_check = tools.email_split_and_format(email)
|
||||
if not email_check:
|
||||
error.append(email)
|
||||
else:
|
||||
valid.extend(email_check)
|
||||
if error:
|
||||
raise UserError(_("Some emails you just entered are incorrect: %s") % (', '.join(error)))
|
||||
self.emails = '\n'.join(valid)
|
||||
|
||||
emails_checked.sort()
|
||||
self.multi_email = '\n'.join(emails_checked)
|
||||
@api.onchange('survey_access_mode')
|
||||
def _onchange_survey_access_mode(self):
|
||||
if self.survey_access_mode == 'internal':
|
||||
return {'domain': {
|
||||
'partner_ids': [('user_ids.groups_id', 'in', [self.env.ref('base.group_user').id])]
|
||||
}}
|
||||
elif self.survey_access_mode == 'authentication':
|
||||
if not self.survey_id.users_can_signup:
|
||||
return {'domain': {
|
||||
'partner_ids': [('user_ids.groups_id', 'in', [self.env.ref('base.group_user').id, self.env.ref('base.group_portal').id])]
|
||||
}}
|
||||
|
||||
@api.onchange('template_id', 'survey_id')
|
||||
@api.onchange('partner_ids')
|
||||
def _onchange_partner_ids(self):
|
||||
if self.survey_access_mode == 'internal' and self.partner_ids:
|
||||
invalid_partners = self.partner_ids.filtered(lambda partner: not partner.user_ids or not all(user.has_group('base.group_user') for user in partner.user_ids))
|
||||
if invalid_partners:
|
||||
raise UserError(
|
||||
_('The following recipients are not valid users belonging to the employee group: %s. You should create user accounts for them.' %
|
||||
(','.join(invalid_partners.mapped('name')))))
|
||||
elif self.survey_access_mode == 'authentication' and self.partner_ids:
|
||||
if not self.survey_id.users_can_signup:
|
||||
invalid_partners = self.env['res.partner'].search([
|
||||
('user_ids', '=', False),
|
||||
('id', 'in', self.partner_ids.ids)
|
||||
])
|
||||
if invalid_partners:
|
||||
raise UserError(
|
||||
_('The following recipients have no user account: %s. You should create user accounts for them or allow external signup in configuration.' %
|
||||
(','.join(invalid_partners.mapped('name')))))
|
||||
|
||||
@api.onchange('template_id')
|
||||
def _onchange_template_id(self):
|
||||
""" UPDATE ME """
|
||||
to_render_fields = [
|
||||
'subject', 'body_html',
|
||||
'email_from', 'reply_to',
|
||||
'partner_to', 'email_to', 'email_cc',
|
||||
'attachment_ids',
|
||||
'mail_server_id'
|
||||
]
|
||||
returned_fields = to_render_fields + ['partner_ids', 'attachments']
|
||||
for wizard in self:
|
||||
if wizard.template_id:
|
||||
values = {}
|
||||
if self.template_id:
|
||||
self.subject = self.template_id.subject
|
||||
self.body = self.template_id.body_html
|
||||
|
||||
template_values = wizard.template_id.generate_email([wizard.survey_id.id], fields=to_render_fields)[wizard.survey_id.id]
|
||||
values = dict((field, template_values[field]) for field in returned_fields if template_values.get(field))
|
||||
values['body'] = values.pop('body_html', '')
|
||||
|
||||
# transform attachments into attachment_ids; not attached to the document because this will
|
||||
# be done further in the posting process, allowing to clean database if email not send
|
||||
Attachment = self.env['ir.attachment']
|
||||
for attach_fname, attach_datas in values.pop('attachments', []):
|
||||
data_attach = {
|
||||
'name': attach_fname,
|
||||
'datas': attach_datas,
|
||||
'datas_fname': attach_fname,
|
||||
'res_model': 'mail.compose.message', # TDE CHECKME
|
||||
'res_id': 0,
|
||||
'type': 'binary', # override default_type from context, possibly meant for another model!
|
||||
}
|
||||
values.setdefault('attachment_ids', list()).append(Attachment.create(data_attach).id)
|
||||
else:
|
||||
default_values = self.default_get(returned_fields)
|
||||
values = dict((key, default_values[key]) for key in returned_fields if key in default_values)
|
||||
|
||||
# This onchange should return command instead of ids for x2many field.
|
||||
# ORM handle the assignation of command list on new onchange (api.v8),
|
||||
# this force the complete replacement of x2many field with
|
||||
# command and is compatible with onchange api.v7
|
||||
values = self._convert_to_write(values)
|
||||
wizard.update(values)
|
||||
@api.model
|
||||
def create(self, values):
|
||||
if values.get('template_id') and not (values.get('body') or values.get('subject')):
|
||||
template = self.env['mail.template'].browse(values['template_id'])
|
||||
if not values.get('subject'):
|
||||
values['subject'] = template.subject
|
||||
if not values.get('body'):
|
||||
values['body'] = template.body_html
|
||||
return super(SurveyInvite, self).create(values)
|
||||
|
||||
#------------------------------------------------------
|
||||
# Wizard validation and send
|
||||
#------------------------------------------------------
|
||||
|
||||
@api.multi
|
||||
def send_mail_action(self):
|
||||
return self.send_mail()
|
||||
def _prepare_answers(self, partners, emails):
|
||||
answers = self.env['survey.user_input']
|
||||
existing_answers = self.env['survey.user_input'].search([
|
||||
'&', ('survey_id', '=', self.survey_id.id),
|
||||
'|',
|
||||
('partner_id', 'in', partners.ids),
|
||||
('email', 'in', emails)
|
||||
])
|
||||
partners_done = self.env['res.partner']
|
||||
emails_done = []
|
||||
if existing_answers:
|
||||
if self.existing_mode == 'prevent':
|
||||
raise UserError(_('Some of recipients already started survey. Please update recipients list accordingly.'))
|
||||
|
||||
if self.existing_mode in ['skip', 'reset']:
|
||||
partners_done = existing_answers.mapped('partner_id')
|
||||
emails_done = existing_answers.mapped('email')
|
||||
|
||||
if self.existing_mode == 'reset':
|
||||
existing_answers.write({
|
||||
'deadline': self.deadline,
|
||||
'state': 'new',
|
||||
'user_input_line_ids': [(5, 0)],
|
||||
})
|
||||
answers |= existing_answers
|
||||
|
||||
for new_partner in partners - partners_done:
|
||||
answers |= self.survey_id._create_answer(partner=new_partner, **self._get_answers_values())
|
||||
for new_email in [email for email in emails if email not in emails_done]:
|
||||
answers |= self.survey_id._create_answer(email=new_email, **self._get_answers_values())
|
||||
|
||||
return answers
|
||||
|
||||
def _get_answers_values(self):
|
||||
return {
|
||||
'deadline': self.deadline,
|
||||
}
|
||||
|
||||
def _send_mail(self, answer):
|
||||
""" Create mail specific for recipient containing notably its access token """
|
||||
subject = self.env['mail.template']._render_template(self.subject, 'survey.user_input', answer.id, post_process=True)
|
||||
body = self.env['mail.template']._render_template(self.body, 'survey.user_input', answer.id, post_process=True)
|
||||
# post the message
|
||||
mail_values = {
|
||||
'email_from': self.email_from,
|
||||
'author_id': self.author_id.id,
|
||||
'model': None,
|
||||
'res_id': None,
|
||||
'subject': subject,
|
||||
'body_html': body,
|
||||
'attachment_ids': [(4, att.id) for att in self.attachment_ids],
|
||||
'auto_delete': True,
|
||||
}
|
||||
if answer.partner_id:
|
||||
mail_values['recipient_ids'] = [(4, answer.partner_id.id)]
|
||||
else:
|
||||
mail_values['email_to'] = answer.email
|
||||
|
||||
# optional support of notif_layout in context
|
||||
notif_layout = self.env.context.get('notif_layout', self.env.context.get('custom_layout'))
|
||||
if notif_layout:
|
||||
try:
|
||||
template = self.env.ref(notif_layout, raise_if_not_found=True)
|
||||
except ValueError:
|
||||
_logger.warning('QWeb template %s not found when sending survey mails. Sending without layouting.' % (notif_layout))
|
||||
else:
|
||||
template_ctx = {
|
||||
'message': self.env['mail.message'].sudo().new(dict(body=mail_values['body_html'], record_name=self.survey_id.title)),
|
||||
'model_description': self.env['ir.model']._get('survey.survey').display_name,
|
||||
'company': self.env.user.company_id,
|
||||
}
|
||||
body = template.render(template_ctx, engine='ir.qweb', minimal_qcontext=True)
|
||||
mail_values['body_html'] = self.env['mail.thread']._replace_local_links(body)
|
||||
|
||||
return self.env['mail.mail'].sudo().create(mail_values)
|
||||
|
||||
@api.multi
|
||||
def send_mail(self, auto_commit=False):
|
||||
def action_invite(self):
|
||||
""" Process the wizard content and proceed with sending the related
|
||||
email(s), rendering any template patterns on the fly if needed """
|
||||
|
||||
SurveyUserInput = self.env['survey.user_input']
|
||||
self.ensure_one()
|
||||
Partner = self.env['res.partner']
|
||||
Mail = self.env['mail.mail']
|
||||
notif_layout = self.env.context.get('notif_layout', self.env.context.get('custom_layout'))
|
||||
|
||||
def create_response_and_send_mail(wizard, token, partner_id, email):
|
||||
""" Create one mail by recipients and replace __URL__ by link with identification token """
|
||||
#set url
|
||||
url = wizard.survey_id.public_url
|
||||
|
||||
if token:
|
||||
url = url + '/' + token
|
||||
|
||||
# post the message
|
||||
values = {
|
||||
'model': None,
|
||||
'res_id': None,
|
||||
'subject': wizard.subject,
|
||||
'body': wizard.body.replace("__URL__", url),
|
||||
'body_html': wizard.body.replace("__URL__", url),
|
||||
'parent_id': None,
|
||||
'attachment_ids': wizard.attachment_ids and [(6, 0, wizard.attachment_ids.ids)] or None,
|
||||
'email_from': wizard.email_from or None,
|
||||
'auto_delete': True,
|
||||
}
|
||||
if partner_id:
|
||||
values['recipient_ids'] = [(4, partner_id)]
|
||||
# compute partners and emails, try to find partners for given emails
|
||||
valid_partners = self.partner_ids
|
||||
valid_emails = []
|
||||
for email in emails_split.split(self.emails or ''):
|
||||
partner = False
|
||||
email_normalized = tools.email_normalize(email)
|
||||
if email_normalized:
|
||||
partner = Partner.search([('email_normalized', '=', email_normalized)])
|
||||
if partner:
|
||||
valid_partners |= partner
|
||||
else:
|
||||
values['email_to'] = email
|
||||
email_formatted = tools.email_split_and_format(email)
|
||||
if email_formatted:
|
||||
valid_emails.extend(email_formatted)
|
||||
|
||||
# optional support of notif_layout in context
|
||||
if notif_layout:
|
||||
try:
|
||||
template = self.env.ref(notif_layout, raise_if_not_found=True)
|
||||
except ValueError:
|
||||
_logger.warning('QWeb template %s not found when sending survey mails. Sending without layouting.' % (notif_layout))
|
||||
else:
|
||||
template_ctx = {
|
||||
'message': self.env['mail.message'].sudo().new(dict(body=values['body_html'], record_name=wizard.survey_id.title)),
|
||||
'model_description': self.env['ir.model']._get('survey.survey').display_name,
|
||||
'company': self.env.user.company_id,
|
||||
}
|
||||
body = template.render(template_ctx, engine='ir.qweb', minimal_qcontext=True)
|
||||
values['body_html'] = self.env['mail.thread']._replace_local_links(body)
|
||||
if not valid_partners and not valid_emails:
|
||||
raise UserError(_("Please enter at least one valid recipient."))
|
||||
|
||||
Mail.create(values).send()
|
||||
|
||||
def create_token(wizard, partner_id, email):
|
||||
if context.get("survey_resent_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 = str(uuid.uuid4())
|
||||
# create response with token
|
||||
survey_user_input = SurveyUserInput.create({
|
||||
'survey_id': wizard.survey_id.id,
|
||||
'deadline': wizard.date_deadline,
|
||||
'date_create': fields.Datetime.now(),
|
||||
'input_type': 'link',
|
||||
'state': 'new',
|
||||
'token': token,
|
||||
'partner_id': partner_id,
|
||||
'email': email})
|
||||
return survey_user_input.token
|
||||
|
||||
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')
|
||||
|
||||
# quick check of email list
|
||||
emails_list = []
|
||||
if wizard.multi_email:
|
||||
emails = set(emails_split.split(wizard.multi_email)) - set(wizard.partner_ids.mapped('email'))
|
||||
for email in emails:
|
||||
email = email.strip()
|
||||
if email_validator.match(email):
|
||||
emails_list.append(email)
|
||||
|
||||
# remove public anonymous access
|
||||
partner_list = []
|
||||
for partner in wizard.partner_ids:
|
||||
partner_list.append({'id': partner.id, 'email': partner.email})
|
||||
|
||||
if not len(emails_list) and not len(partner_list):
|
||||
raise UserError(_("Please enter at least one valid recipient."))
|
||||
|
||||
for email in emails_list:
|
||||
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'])
|
||||
create_response_and_send_mail(wizard, token, partner['id'], partner['email'])
|
||||
answers = self._prepare_answers(valid_partners, valid_emails)
|
||||
for answer in answers:
|
||||
self._send_mail(answer)
|
||||
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
@@ -1,62 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Composition form (context['survey_resent_token'], if True, hide share option, emails list and partner_ids) -->
|
||||
<record model="ir.ui.view" id="survey_invite_view_form">
|
||||
<field name="name">survey.invite.view.form</field>
|
||||
<field name="model">survey.invite</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Compose Email">
|
||||
<group col="2">
|
||||
<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">
|
||||
<div>
|
||||
You can share your survey web public link and/or send private invitations to your audience. People can answer once per invitation, and whenever they want with the public web link (in this case, the "Login Required" setting must be disabled).
|
||||
</div>
|
||||
<div>Copy, paste and share the web link below to your audience.</div>
|
||||
<code><field name="public_url"/></code>
|
||||
<div>Copy and paste the HTML code below to add this web link to any webpage.</div>
|
||||
<code><field name="public_url_html" /></code>
|
||||
</group>
|
||||
<group col="1" attrs="{'invisible':['|',('public','=',False),('public','in',['public_link'])]}">
|
||||
<group col="1">
|
||||
<group col="2">
|
||||
<field name="survey_access_mode" invisible="1"/>
|
||||
<field name="survey_id" readonly="context.get('default_survey_id')"/>
|
||||
<field name="existing_mode" widget="radio" invisible="1" />
|
||||
<field name="survey_url" label="Public share URL" readonly="1" widget="CopyClipboardText"
|
||||
attrs="{'invisible':[('survey_access_mode', '!=', 'public')]}"
|
||||
class="mb16"/>
|
||||
<field name="partner_ids"
|
||||
invisible="context.get('survey_resent_token')"
|
||||
attrs="{'readonly': [('existing_mode', '=', 'resend')]}"
|
||||
widget="many2many_tags_email"
|
||||
placeholder="Add existing contacts..."
|
||||
context="{'force_email':True, 'show_email':True}" />
|
||||
<field name="multi_email"
|
||||
invisible="context.get('survey_resent_token')"
|
||||
context="{'force_email':True, 'show_email':True, 'no_create_edit': True}"/>
|
||||
<field name="emails"
|
||||
attrs="{
|
||||
'invisible': [('survey_access_mode', 'in', ['authentication', 'internal'])],
|
||||
'readonly': [('existing_mode', '=', 'resend')]
|
||||
}"
|
||||
placeholder="Add a list of email of recipients (will not be converted into contacts). Separated by commas, semicolons or newline..."/>
|
||||
<field name="subject" placeholder="Subject..."/>
|
||||
</group>
|
||||
<field name="body" options="{'style-inline': true}"/>
|
||||
<group col="2">
|
||||
<div>
|
||||
<label for="attachment_ids" />
|
||||
<field name="attachment_ids" widget="many2many_binary" />
|
||||
</div>
|
||||
<div class="oe_survey_date_deadline">
|
||||
<label for="date_deadline" />
|
||||
<field name="date_deadline" />
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="attachment_ids" widget="many2many_binary"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="deadline"/>
|
||||
<field name="template_id" label="Use template"/>
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
<footer attrs="{'invisible':[('public','in',['email_private', 'email_public_link'])]}">
|
||||
<button string="Close" class="btn-primary" special="cancel" />
|
||||
</footer>
|
||||
<footer attrs="{'invisible':['|',('public','=',False),('public','in',['public_link'])]}">
|
||||
<button string="Send" name="send_mail_action" type="object" class="btn-primary" />
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" />
|
||||
<group class="oe_right" col="1">
|
||||
<div>
|
||||
Use template
|
||||
<field name="template_id" nolabel="1"/>
|
||||
</div>
|
||||
<button icon="/email_template/static/src/img/email_template_save.png" type="object" name="save_as_template" string="Save as new template" class="oe_link" help="Save as a new template" />
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Send" name="action_invite" type="object" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
|
||||
Reference in New Issue
Block a user