diff --git a/addons/survey/models/survey_page.py b/addons/survey/models/survey_page.py index b518ff01f16..bda0f551dbf 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 3f207c2719e..b018a7a8b5a 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,12 +77,11 @@ 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.")) + 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)]) _sql_constraints = [ @@ -277,9 +260,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 b73e28fbc51..f2d415340fd 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 2e8e90b48fa..a88cec640bd 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,27 @@ class Survey(models.Model): def _default_stage(self): return self.env['survey.stage'].search([], limit=1).id + # description title = fields.Char('Title', required=True, translate=True) - page_ids = fields.One2many('survey.page', 'survey_id', string='Pages', copy=True) - stage_id = fields.Many2one('survey.stage', string="Stage", default=_default_stage, - ondelete="restrict", copy=False, group_expand='_read_group_stage_ids') - auth_required = fields.Boolean('Login required', help="Users with a public link will be requested to login before taking part to the survey", - oldname="authenticate") - users_can_go_back = fields.Boolean('Users can go back', help="If checked, users can go back to previous pages.") - tot_sent_survey = fields.Integer("Number of sent surveys", compute="_compute_survey_statistic") - tot_start_survey = fields.Integer("Number of started surveys", compute="_compute_survey_statistic") - tot_comp_survey = fields.Integer("Number of completed surveys", compute="_compute_survey_statistic") description = fields.Html("Description", translate=True, help="A long description of the purpose of the survey") color = fields.Integer('Color Index', default=0) - user_input_ids = fields.One2many('survey.user_input', 'survey_id', string='User responses', readonly=True) - designed = fields.Boolean("Is designed?", compute="_is_designed") - public_url = fields.Char("Public link", compute="_compute_survey_url") - public_url_html = fields.Char("Public link (html version)", compute="_compute_survey_url") - print_url = fields.Char("Print link", compute="_compute_survey_url") - result_url = fields.Char("Results link", compute="_compute_survey_url") - email_template_id = fields.Many2one('mail.template', string='Email Template', ondelete='set null') thank_you_message = fields.Html("Thanks Message", translate=True, help="This message will be displayed when survey is completed") quizz_mode = fields.Boolean("Quizz Mode") active = fields.Boolean("Active", default=True) + stage_id = fields.Many2one('survey.stage', string="Stage", default=_default_stage, + ondelete="restrict", copy=False, group_expand='_read_group_stage_ids') is_closed = fields.Boolean("Is closed", related='stage_id.closed', readonly=False) - - def _is_designed(self): - 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 + # 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) + # security / access + auth_required = fields.Boolean('Login required', help="Users with a public link will be requested to login before taking part to the survey") + users_can_go_back = fields.Boolean('Users can go back', help="If checked, users can go back to previous pages.") + 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") @api.multi def _compute_survey_statistic(self): @@ -70,13 +58,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 = '%s' % (survey.public_url, _("Click here to start survey")) @api.model def _read_group_stage_ids(self, stages, domain, order): @@ -273,7 +257,7 @@ class Survey(models.Model): '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 @@ -317,7 +301,7 @@ class Survey(models.Model): '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 +312,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 4efe74a9c43..edc903c85fd 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' + # description 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") + 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="Date by which the person 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,8 +64,9 @@ 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): @@ -85,38 +81,26 @@ class SurveyUserInput(models.Model): return self.survey_id.with_context(local_context).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?token=%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) 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/tests/test_survey.py b/addons/survey/tests/test_survey.py index 0ce7c5d612d..b962a4fa1dd 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 = 'Click here to start survey' - 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/views/survey_survey_views.xml b/addons/survey/views/survey_survey_views.xml index 6e10e60398f..7c0d391b4af 100644 --- a/addons/survey/views/survey_survey_views.xml +++ b/addons/survey/views/survey_survey_views.xml @@ -80,14 +80,12 @@ - - @@ -111,12 +109,10 @@
  • - Test - Test + Test
  • - Share &amp; Invite - Share &amp; Invite + Share &amp; Invite
  • Analyze Answers () diff --git a/addons/survey/views/survey_templates.xml b/addons/survey/views/survey_templates.xml index 2b8149d37f0..37795950cfb 100644 --- a/addons/survey/views/survey_templates.xml +++ b/addons/survey/views/survey_templates.xml @@ -461,20 +461,20 @@ - + -
    +
    -
    +
    -
    +
    @@ -495,7 +495,7 @@ - +
    @@ -679,7 +679,7 @@ - +
    diff --git a/addons/survey/views/survey_user_views.xml b/addons/survey/views/survey_user_views.xml index 1e35a182e06..919daed1deb 100644 --- a/addons/survey/views/survey_user_views.xml +++ b/addons/survey/views/survey_user_views.xml @@ -30,8 +30,7 @@
    @@ -39,7 +38,7 @@ - + @@ -57,7 +56,7 @@ - + @@ -71,7 +70,7 @@ - + @@ -89,7 +88,7 @@ - + @@ -105,7 +104,7 @@
- +
@@ -155,7 +154,7 @@ - + @@ -181,7 +180,7 @@ - + diff --git a/addons/survey/wizard/survey_invite.py b/addons/survey/wizard/survey_invite.py index 7aa47259262..9190d67b6b7 100644 --- a/addons/survey/wizard/survey_invite.py +++ b/addons/survey/wizard/survey_invite.py @@ -64,7 +64,6 @@ class SurveyInvite(models.TransientModel): ('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.") @@ -73,7 +72,6 @@ class SurveyInvite(models.TransientModel): 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): @@ -219,7 +217,6 @@ class SurveyInvite(models.TransientModel): 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, diff --git a/addons/survey/wizard/survey_invite_views.xml b/addons/survey/wizard/survey_invite_views.xml index 942714e0f43..2d0edef97e7 100644 --- a/addons/survey/wizard/survey_invite_views.xml +++ b/addons/survey/wizard/survey_invite_views.xml @@ -17,8 +17,6 @@
Copy, paste and share the web link below to your audience.
-
Copy and paste the HTML code below to add this web link to any webpage.
-