diff --git a/addons/hr_recruitment_survey/__manifest__.py b/addons/hr_recruitment_survey/__manifest__.py
index 6dcce9a459c8f83065c7581cdc4167f03ed85514..6d04b788b10839ef738c2cae3dc837e31a54767e 100644
--- a/addons/hr_recruitment_survey/__manifest__.py
+++ b/addons/hr_recruitment_survey/__manifest__.py
@@ -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': [
diff --git a/addons/hr_recruitment_survey/data/survey_demo.xml b/addons/hr_recruitment_survey/data/survey_demo.xml
index 089b5df481b1dd2484cee8cf751da77d4ce18bdd..f7480ec6d6bb10b71f45c713e99be22b682ff2e3 100644
--- a/addons/hr_recruitment_survey/data/survey_demo.xml
+++ b/addons/hr_recruitment_survey/data/survey_demo.xml
@@ -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>
diff --git a/addons/hr_recruitment_survey/models/__init__.py b/addons/hr_recruitment_survey/models/__init__.py
index 3d3ce8e9b0b64e88721511a8abeaebc6646d65fc..dc295255d0ca54f97a8f5243c37177366685128d 100644
--- a/addons/hr_recruitment_survey/models/__init__.py
+++ b/addons/hr_recruitment_survey/models/__init__.py
@@ -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
diff --git a/addons/hr_recruitment_survey/models/hr_applicant.py b/addons/hr_recruitment_survey/models/hr_applicant.py
index 46fb9445da0cc4e007175eb42c82d208222d29b9..bd784622dc181a11a8fb4ca5f8b38dfd5bcaaf5d 100644
--- a/addons/hr_recruitment_survey/models/hr_applicant.py
+++ b/addons/hr_recruitment_survey/models/hr_applicant.py
@@ -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
diff --git a/addons/hr_recruitment_survey/models/hr_job.py b/addons/hr_recruitment_survey/models/hr_job.py
index 9f424d34b52d5b7d383f8188222775c0116f004a..eb24574e55a028e57cc5265711124d16f1d9c022 100644
--- a/addons/hr_recruitment_survey/models/hr_job.py
+++ b/addons/hr_recruitment_survey/models/hr_job.py
@@ -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
diff --git a/addons/hr_recruitment_survey/models/survey_survey.py b/addons/hr_recruitment_survey/models/survey_survey.py
new file mode 100644
index 0000000000000000000000000000000000000000..21674e84ee6fd672450e98ea55f3abf9c4472794
--- /dev/null
+++ b/addons/hr_recruitment_survey/models/survey_survey.py
@@ -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')])
diff --git a/addons/hr_recruitment_survey/security/ir.model.access.csv b/addons/hr_recruitment_survey/security/ir.model.access.csv
deleted file mode 100644
index e9b9241d64939259a9d8aec2315c8cc3e6486289..0000000000000000000000000000000000000000
--- a/addons/hr_recruitment_survey/security/ir.model.access.csv
+++ /dev/null
@@ -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
diff --git a/addons/hr_recruitment_survey/views/hr_job_views.xml b/addons/hr_recruitment_survey/views/hr_job_views.xml
index 1cab317718c22f95b7462ae56469756adf0dd71b..7223f1c683443742689ef70ba79f869a9401ff00 100644
--- a/addons/hr_recruitment_survey/views/hr_job_views.xml
+++ b/addons/hr_recruitment_survey/views/hr_job_views.xml
@@ -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>
diff --git a/addons/hr_recruitment_survey/views/survey_survey_views.xml b/addons/hr_recruitment_survey/views/survey_survey_views.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a6f51da722ebc789e1f0c3c9224578e86d5683f6
--- /dev/null
+++ b/addons/hr_recruitment_survey/views/survey_survey_views.xml
@@ -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>
diff --git a/addons/survey/__manifest__.py b/addons/survey/__manifest__.py
index 9c2617434ac82c1660a10d6e40a11b2bcb5bd070..91e92eb3c28000645c381b74b1a960c5e89e864c 100644
--- a/addons/survey/__manifest__.py
+++ b/addons/survey/__manifest__.py
@@ -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',
diff --git a/addons/survey/controllers/main.py b/addons/survey/controllers/main.py
index 80ee03d0dcf042f6b0131d8f3da1d8d35d077595..712edc23908c3b357bc3c81470aab3e2fc820a5e 100644
--- a/addons/survey/controllers/main.py
+++ b/addons/survey/controllers/main.py
@@ -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/")
-
-        # 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})
-
-        # In case of non open surveys
-        if survey.stage_id.closed:
-            return request.render("survey.notopen")
-
-        # If there is no pages
-        if not survey.page_ids:
-            return request.render("survey.nopages", {'survey': 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
+
+    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
+
+         * 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;
+
+        :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)
+
+        if not survey_sudo.exists():
+            return 'survey_wrong'
+
+        if access_token and not answer_sudo:
+            return 'token_wrong'
+
+        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 > answer_sudo.deadline:
+                return 'answer_deadline'
 
-        # Everything seems to be ok
-        return None
+        return True
 
-    def _check_deadline(self, user_input):
-        '''Prevent opening of the survey if the deadline has turned out
+    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.
 
-        ! 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)
-            dt_now = datetime.now()
-            if dt_now > dt_deadline:  # survey is not open anymore
-                return request.render("survey.notopen")
-        return None
+         : 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
 
-    @http.route('/survey/test/<model("survey.survey"):survey>', type='http', auth='user', website=True)
-    def survey_test(self, survey, token=None, **kwargs):
+        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('/')
-
-    @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):
+        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/<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']
-
-        # Controls if the survey can be displayed
-        errpage = self._check_bad_cases(survey, token=token)
-        if errpage:
-            return errpage
-
-        # 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})
+        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'])
+
+        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
 
-        # 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))
-
-    # 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']
+            return request.redirect('/survey/fill/%s/%s' % (survey_sudo.id, answer_sudo.token))
 
-        # Controls if the survey can be displayed
-        errpage = self._check_bad_cases(survey)
-        if errpage:
-            return errpage
+    @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'])
 
-        # 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})
-
-    # 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 = {}
+            return request.render("survey.403", {'survey': survey_sudo})
+
+    @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'])
+
+        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_reporting(self, survey, token=None, **post):
+    @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 = []
diff --git a/addons/survey/data/mail_template_data.xml b/addons/survey/data/mail_template_data.xml
index 08b6d4403c43d8e0adf3a26979514d37018db587..66f81771c74c9fe6544b8afb94b098b831269812 100644
--- a/addons/survey/data/mail_template_data.xml
+++ b/addons/survey/data/mail_template_data.xml
@@ -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>
diff --git a/addons/survey/data/survey_demo_feedback.xml b/addons/survey/data/survey_demo_feedback.xml
index 24aa3890ac4d5e96ea44ac4810e32838b15234d3..8d24117ab474bc658238374967d4100787909572 100644
--- a/addons/survey/data/survey_demo_feedback.xml
+++ b/addons/survey/data/survey_demo_feedback.xml
@@ -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>
\ No newline at end of file
diff --git a/addons/survey/models/survey_page.py b/addons/survey/models/survey_page.py
index b518ff01f16a19613e959b4850f10f9c1e7e0d64..bda0f551dbfe5ff73d72ffdc262385d5501d1522 100644
--- a/addons/survey/models/survey_page.py
+++ b/addons/survey/models/survey_page.py
@@ -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)
diff --git a/addons/survey/models/survey_question.py b/addons/survey/models/survey_question.py
index 3f207c2719e9b427543746d18e7b567e227c613b..5014c96e0177cd48a66c0293b073286a9fd76bdf 100644
--- a/addons/survey/models/survey_question.py
+++ b/addons/survey/models/survey_question.py
@@ -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'
diff --git a/addons/survey/models/survey_stage.py b/addons/survey/models/survey_stage.py
index b73e28fbc519ea9a46e439e92197c200de7f2f23..f2d415340fd1fa20b16ec9512150f2a7db830790 100644
--- a/addons/survey/models/survey_stage.py
+++ b/addons/survey/models/survey_stage.py
@@ -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'
diff --git a/addons/survey/models/survey_survey.py b/addons/survey/models/survey_survey.py
index 01e0b7b8eec7733c803c76cfff21b522c8f524ac..b1b055a47aaea72807dfa516c0ec1385df32dc29 100644
--- a/addons/survey/models/survey_survey.py
+++ b/addons/survey/models/survey_survey.py
@@ -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)
+    description = fields.Html("Description", translate=True, help="A long description of the purpose of the survey")
+    color = fields.Integer('Color Index', default=0)
+    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)
     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")
+    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")
-    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)
 
-    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
diff --git a/addons/survey/models/survey_user.py b/addons/survey/models/survey_user.py
index 32250825bd3213bf64f639d72b4254bb2f6c0bef..d034098692b56cdeea8a74e3dcd031fc4cafbf86 100644
--- a/addons/survey/models/survey_user.py
+++ b/addons/survey/models/survey_user.py
@@ -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'),
diff --git a/addons/survey/security/ir.model.access.csv b/addons/survey/security/ir.model.access.csv
index 09b5ae48178542af5ed01872bfbdae3dd0cb1397..e57a716cd2306d2db198cab3e03379ea0af9528b 100644
--- a/addons/survey/security/ir.model.access.csv
+++ b/addons/survey/security/ir.model.access.csv
@@ -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
\ No newline at end of file
+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
diff --git a/addons/survey/security/survey_security.xml b/addons/survey/security/survey_security.xml
index 5d79c515d268bf3c89b32286b79990b2d2fc6d65..cf4e02e918360390c6b089464361768701660911 100644
--- a/addons/survey/security/survey_security.xml
+++ b/addons/survey/security/survey_security.xml
@@ -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>
+        <!-- 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">[('stage_id.closed', '=', False)]</field>
+            <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_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 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="0"/>
+            <field name="perm_write" eval="0"/>
+            <field name="perm_read" eval="1"/>
+            <field name="perm_create" eval="0"/>
         </record>
-
-        <record id="survey_manager_access" model="ir.rule">
-            <field name="name">Survey Manager access rights</field>
+        <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('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_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 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_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_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"/>
-            <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 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_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>
+        <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 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="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_input_manager_access" model="ir.rule">
-            <field name="name">Survey Manager access rights</field>
+        <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>
diff --git a/addons/survey/tests/__init__.py b/addons/survey/tests/__init__.py
index b345311e36bede0872c5b6588cf60141af139e4a..0f0a3bd31caccdf899bc28e790562497e5e63a41 100644
--- a/addons/survey/tests/__init__.py
+++ b/addons/survey/tests/__init__.py
@@ -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
diff --git a/addons/survey/tests/common.py b/addons/survey/tests/common.py
index fbe03f3f24b3fc78f346543324f0f106c4e5695e..4235739ba147c9a4a27c8eec4e40fc2e2473ab08 100644
--- a/addons/survey/tests/common.py
+++ b/addons/survey/tests/common.py
@@ -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({
diff --git a/addons/survey/tests/test_survey.py b/addons/survey/tests/test_survey.py
index 0ce7c5d612db16fbd060b2ead874c764dfd6f54c..b962a4fa1dd147a7c057c35732e1324ba1e6b98d 100644
--- a/addons/survey/tests/test_survey.py
+++ b/addons/survey/tests/test_survey.py
@@ -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'))
diff --git a/addons/survey/tests/test_survey_flow.py b/addons/survey/tests/test_survey_flow.py
index 12230d491942912c320ce893e0381eb88e874cc9..11d9ed2a57c8b6a3a401a819f45abee6500a03d6 100644
--- a/addons/survey/tests/test_survey_flow.py
+++ b/addons/survey/tests/test_survey_flow.py
@@ -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
 
diff --git a/addons/survey/tests/test_survey_invite.py b/addons/survey/tests/test_survey_invite.py
index b2f464dcf9884fcb3e7beda6d56c6c0d75232ea8..92c74bc89a879725faa88fa8365924564f7520aa 100644
--- a/addons/survey/tests/test_survey_invite.py
+++ b/addons/survey/tests/test_survey_invite.py
@@ -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)
diff --git a/addons/survey/tests/test_survey_security.py b/addons/survey/tests/test_survey_security.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ac3db41382b8317f76b6d51effe9d20871bc4c2
--- /dev/null
+++ b/addons/survey/tests/test_survey_security.py
@@ -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()
diff --git a/addons/survey/tests/test_survey_ui.py b/addons/survey/tests/test_survey_ui.py
index 3e489ca7be72f4d22e7a6a3b087d6cf4bd019f15..5005ac71bcfe92d5b025b79ee4d9b55e18db8c22 100644
--- a/addons/survey/tests/test_survey_ui.py
+++ b/addons/survey/tests/test_survey_ui.py
@@ -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")
diff --git a/addons/survey/views/survey_survey_views.xml b/addons/survey/views/survey_survey_views.xml
index 6e10e60398f7c4e4e7205f6b4e7d990668dd79d7..04dd90c305e5dbe95f05cbfd524bbd413338f8ab 100644
--- a/addons/survey/views/survey_survey_views.xml
+++ b/addons/survey/views/survey_survey_views.xml
@@ -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;amp; Invite</a></t>
-                                    <t t-if="! record.designed.raw_value"><a style="color: #aaaaaa;">Share &amp;amp; Invite</a></t>
+                                    <a type="object" name="action_send_survey">Share &amp;amp; Invite</a>
                                 </li>
                                 <li>
                                     <t t-if="record.tot_start_survey.raw_value &gt; 0"><a name="action_result_survey" type="object">Analyze Answers</a> <span t-if="record.tot_start_survey.raw_value &gt; 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', '&gt;', 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"/>
 
diff --git a/addons/survey/views/survey_templates.xml b/addons/survey/views/survey_templates.xml
index 203c7f77d3b0ea76441cd773885a13487dfca94e..4bcd81ba4621b957671696ea05a800adc4b69bce 100644
--- a/addons/survey/views/survey_templates.xml
+++ b/addons/survey/views/survey_templates.xml
@@ -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&amp;model=survey.survey&amp;id=%s&amp;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>
diff --git a/addons/survey/views/survey_user_views.xml b/addons/survey/views/survey_user_views.xml
index 1e35a182e068d1778816a4ab923b687c25831d50..dfa5aa4aa0b428ee657546ca25785b9df19a688a 100644
--- a/addons/survey/views/survey_user_views.xml
+++ b/addons/survey/views/survey_user_views.xml
@@ -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'), '&amp;', ('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"/>
diff --git a/addons/survey/wizard/survey_invite.py b/addons/survey/wizard/survey_invite.py
index 7aa472592621194d9c1d61ba18021f84fae27817..98d45dc062656df2daac8f8efd074272ed5b31b0 100644
--- a/addons/survey/wizard/survey_invite.py
+++ b/addons/survey/wizard/survey_invite.py
@@ -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.")
-
-    @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 = ""
+    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.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)
-
-        emails_checked.sort()
-        self.multi_email = '\n'.join(emails_checked)
-
-    @api.onchange('template_id', 'survey_id')
+            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)
+
+    @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('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
-
-            # 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)
-
-            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."))
+                email_formatted = tools.email_split_and_format(email)
+                if email_formatted:
+                    valid_emails.extend(email_formatted)
 
-            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)
+        if not valid_partners and not valid_emails:
+            raise UserError(_("Please enter at least one valid recipient."))
 
-            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'}
diff --git a/addons/survey/wizard/survey_invite_views.xml b/addons/survey/wizard/survey_invite_views.xml
index 942714e0f43705eb37315040b43c21b808b5cf71..f9e19be04ee291817361fb18b9edd3f47f991cd8 100644
--- a/addons/survey/wizard/survey_invite_views.xml
+++ b/addons/survey/wizard/survey_invite_views.xml
@@ -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>