summaryrefslogtreecommitdiff
path: root/addons/website_slides/models
diff options
context:
space:
mode:
authorqmo-odoo <qmo@odoo.com>2018-11-21 09:06:40 +0000
committerThibault Delavallée <tde@odoo.com>2019-02-19 13:11:15 +0000
commitc564ae6c87a79832d94de99d650d437fcf68b1c1 (patch)
tree93a3afba839f793db887dbb0947d2eb92ce55f8d /addons/website_slides/models
parent7a80ebe35edba3d76ca7167a2ef13e26b1cac9d4 (diff)
[IMP] website_slides: Add elearning feature to slides
Purpose of this commit is to introduce a new way of displaying and managing slides in eLearning module. Its purpose is to give a better experience to users when going through a course. Notably a fullscreen mode allows to take lessons one by one without going out of the elearning display. Integration of all slide types eases taking the course step by step. Quiz are introduced in this commit. Those are an addition to slides with some question / answers (multiple choice). It allows customer to gain karma and improves gamification. Certifications are still done using the survey application. Quiz targets only small tests at the end of a given slide. A new widget is added to display and control slide display in fullscreen mode. Old display is still available for documentation channels or when going out of fullscreen mode, to see details on comments / review / statistics and have access to share options. This commit is linked to task ID 1902304 and PR #29876. It closes the main work on refactoring website slides into eLearning, with other tasks already merged in current community [1][2][3][4][5]. [1] Task ID 1940360 landed at b2149bc7a274217a0b96e587abd52093bd98e5e8: certification inclusion [2] Task ID 1936153 landed at 6c6179e57cede3d9889c33873740a7b1ddecda2e: homepage inclusion [3] Task ID 1937160 landed at 8b7605c9e5df4a700b0139d1781db24ae5d31626: selling courses [4] Task ID 1922159 and 1940516 landed at 5fa651cb013ac67d7ba592498c4cf2770dec2483 and 6a64c3ce821f080969e49e5e760e52ae9bfc26cc: new user profile and gamification of slides / forum [5] Task ID 1938643 landed at 77b5673a0dd220b3c28e88f8acbb5274b486c5bd: upload channel/slide
Diffstat (limited to 'addons/website_slides/models')
-rw-r--r--addons/website_slides/models/__init__.py1
-rw-r--r--addons/website_slides/models/slide_channel.py9
-rw-r--r--addons/website_slides/models/slide_question.py51
-rw-r--r--addons/website_slides/models/slide_slide.py22
4 files changed, 77 insertions, 6 deletions
diff --git a/addons/website_slides/models/__init__.py b/addons/website_slides/models/__init__.py
index 4b8210c6caa..0fcfe6373ab 100644
--- a/addons/website_slides/models/__init__.py
+++ b/addons/website_slides/models/__init__.py
@@ -2,6 +2,7 @@
from . import gamification_challenge
from . import slide_slide
+from . import slide_question
from . import slide_channel
from . import slide_channel_tag
from . import slide_channel_invite
diff --git a/addons/website_slides/models/slide_channel.py b/addons/website_slides/models/slide_channel.py
index dd772a8ec7b..6bbd0e3b297 100644
--- a/addons/website_slides/models/slide_channel.py
+++ b/addons/website_slides/models/slide_channel.py
@@ -101,6 +101,7 @@ class Channel(models.Model):
nbr_videos = fields.Integer('Number of Videos', compute='_compute_slides_statistics', store=True)
nbr_infographics = fields.Integer('Number of Infographics', compute='_compute_slides_statistics', store=True)
nbr_webpages = fields.Integer("Number of Webpages", compute='_compute_slides_statistics', store=True)
+ nbr_quizs = fields.Integer("Number of quizzes", compute="_compute_slides_statistics", store=True)
total_slides = fields.Integer('# Slides', compute='_compute_slides_statistics', store=True, oldname='total')
total_views = fields.Integer('# Views', compute='_compute_slides_statistics', store=True)
total_votes = fields.Integer('# Votes', compute='_compute_slides_statistics', store=True)
@@ -174,7 +175,7 @@ class Channel(models.Model):
def _compute_slides_statistics(self):
result = dict((cid, dict(total_slides=0, total_views=0, total_votes=0, total_time=0)) for cid in self.ids)
read_group_res = self.env['slide.slide'].read_group(
- [('is_published', '=', True), ('channel_id', 'in', self.ids)],
+ [('is_published', '=', True),('channel_id', 'in', self.ids)],
['channel_id', 'slide_type', 'likes', 'dislikes', 'total_views', 'completion_time'],
groupby=['channel_id', 'slide_type'],
lazy=False)
@@ -195,7 +196,7 @@ class Channel(models.Model):
def _compute_slides_statistics_type(self, read_group_res):
""" Can be overridden to compute stats on added slide_types """
- result = dict((cid, dict(nbr_presentations=0, nbr_documents=0, nbr_videos=0, nbr_infographics=0, nbr_webpages=0)) for cid in self.ids)
+ result = dict((cid, dict(nbr_presentations=0, nbr_documents=0, nbr_videos=0, nbr_infographics=0, nbr_webpages=0, nbr_quizs=0)) for cid in self.ids)
for res_group in read_group_res:
cid = res_group['channel_id'][0]
result[cid]['nbr_presentations'] += res_group.get('slide_type', '') == 'presentation' and res_group['__count'] or 0
@@ -203,6 +204,7 @@ class Channel(models.Model):
result[cid]['nbr_videos'] += res_group.get('slide_type', '') == 'video' and res_group['__count'] or 0
result[cid]['nbr_infographics'] += res_group.get('slide_type', '') == 'infographic' and res_group['__count'] or 0
result[cid]['nbr_webpages'] += res_group.get('slide_type', '') == 'webpage' and res_group['__count'] or 0
+ result[cid]['nbr_quizs'] += res_group.get('slide_type', '') == 'quiz' and res_group['__count'] or 0
return result
@api.depends('slide_partner_ids')
@@ -409,6 +411,7 @@ class Category(models.Model):
nbr_videos = fields.Integer("Number of Videos", compute='_count_presentations', store=True)
nbr_infographics = fields.Integer("Number of Infographics", compute='_count_presentations', store=True)
nbr_webpages = fields.Integer("Number of Webpages", compute='_count_presentations', store=True)
+ nbr_quizs = fields.Integer("Number of quizzes", compute="_count_presentations", store=True)
total_slides = fields.Integer(compute='_count_presentations', store=True, oldname='total')
@api.depends('slide_ids.slide_type', 'slide_ids.is_published')
@@ -432,6 +435,7 @@ class Category(models.Model):
'nbr_videos': result[record_id].get('video', 0),
'nbr_infographics': result[record_id].get('infographic', 0),
'nbr_webpages': result[record_id].get('webpage', 0),
+ 'nbr_quizs': result[record_id].get('quiz', 0),
'total_slides': 0
}
@@ -440,5 +444,6 @@ class Category(models.Model):
statistics['total_slides'] += statistics['nbr_videos']
statistics['total_slides'] += statistics['nbr_infographics']
statistics['total_slides'] += statistics['nbr_webpages']
+ statistics['total_slides'] += statistics['nbr_quizs']
return statistics
diff --git a/addons/website_slides/models/slide_question.py b/addons/website_slides/models/slide_question.py
new file mode 100644
index 00000000000..0193558ba46
--- /dev/null
+++ b/addons/website_slides/models/slide_question.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models, _
+from odoo.exceptions import ValidationError
+
+
+class SlideQuestion(models.Model):
+ _name = "slide.question"
+ _rec_name = "question"
+ _description = """
+ Question for a slide. A slide can have multiple questions and each question
+ must have at least 2 possible answers and only one good answer.
+ """
+
+ sequence = fields.Integer("Sequence", default=10)
+ question = fields.Char("Question Name", required=True)
+ slide_id = fields.Many2one('slide.slide', string="Slide")
+ answer_ids = fields.One2many('slide.answer', 'question_id', string="Answer")
+
+ @api.constrains('answer_ids')
+ def _check_only_one_good_answer(self):
+ for question in self:
+ good_answer_count = 0
+ for answer in question.answer_ids:
+ if answer.is_correct:
+ good_answer_count += 1
+ if good_answer_count > 1:
+ raise ValidationError(_('A question can only have one good answer'))
+
+ @api.constrains('answer_ids')
+ def _check_correct_answer(self):
+ for question in self:
+ if not any([answer.is_correct for answer in question.answer_ids]):
+ raise ValidationError(_("A question must at least have one good answer"))
+
+ @api.constrains('answer_ids')
+ def _check_at_least_2_answers(self):
+ for question in self:
+ if len(question.answer_ids) < 2:
+ raise ValidationError(_("A question must at least have two possible answers"))
+
+
+class SlideAnswer(models.Model):
+ _name = "slide.answer"
+ _rec_name = "text_value"
+ _description = """Answer for a slide question"""
+
+ question_id = fields.Many2one('slide.question', string="Question")
+ text_value = fields.Char("Answer", required=True,)
+ is_correct = fields.Boolean("Is correct answer", default=False)
diff --git a/addons/website_slides/models/slide_slide.py b/addons/website_slides/models/slide_slide.py
index 35b9fefc4e5..a71446b82fa 100644
--- a/addons/website_slides/models/slide_slide.py
+++ b/addons/website_slides/models/slide_slide.py
@@ -23,12 +23,14 @@ class SlidePartnerRelation(models.Model):
_description = 'Slide / Partner decorated m2m'
_table = 'slide_slide_partner'
- slide_id = fields.Many2one('slide.slide', index=True, required=True)
+ slide_id = fields.Many2one('slide.slide', ondelete="cascade", index=True, required=True)
channel_id = fields.Many2one('slide.channel', string="Channel", related="slide_id.channel_id", store=True, index=True)
partner_id = fields.Many2one('res.partner', index=True, required=True)
vote = fields.Integer('Vote', default=0)
completed = fields.Boolean('Completed')
+ quiz_attempts_count = fields.Integer(string="Number of times this user attempted the quiz")
+
class SlideLink(models.Model):
_name = 'slide.slide.link'
@@ -95,6 +97,7 @@ class Slide(models.Model):
'most_voted': 'likes desc',
'latest': 'date_published desc',
}
+ _order = 'category_sequence asc, sequence asc'
_sql_constraints = [
('exclusion_html_content_and_url', "CHECK(html_content IS NULL OR url IS NULL)", "A slide is either filled with a document url or HTML content. Not both.")
@@ -106,6 +109,8 @@ class Slide(models.Model):
# description
name = fields.Char('Title', required=True, translate=True)
active = fields.Boolean(default=True)
+ sequence = fields.Integer('Sequence', default=10)
+ category_sequence = fields.Integer('Category sequence', related="category_id.sequence", store=True)
user_id = fields.Many2one('res.users', string='Uploaded by', default=lambda self: self.env.uid)
description = fields.Text('Description', translate=True)
channel_id = fields.Many2one('slide.channel', string="Channel", required=True)
@@ -126,15 +131,24 @@ class Slide(models.Model):
partner_ids = fields.Many2many('res.partner', 'slide_slide_partner', 'slide_id', 'partner_id',
string='Subscribers', groups='base.group_website_publisher')
slide_partner_ids = fields.One2many('slide.slide.partner', 'slide_id', string='Subscribers information', groups='base.group_website_publisher')
- user_membership_id = fields.Many2one('slide.slide.partner', string="Subscriber information", compute='_compute_user_membership_id',
+ user_membership_id = fields.Many2one(
+ 'slide.slide.partner', string="Subscriber information", compute='_compute_user_membership_id',
help="Subscriber information for the current logged in user")
+ # Quiz related fields
+ question_ids = fields.One2many("slide.question","slide_id", string="Questions")
+ quiz_first_attempt_reward = fields.Integer("First attempt reward", default=10)
+ quiz_second_attempt_reward = fields.Integer("Second attempt reward", default=7)
+ quiz_third_attempt_reward = fields.Integer("Third attempt reward", default=5,)
+ quiz_fourth_attempt_reward = fields.Integer("Reward for every attempt after the third try", default=2)
+
# content
slide_type = fields.Selection([
('infographic', 'Infographic'),
+ ('webpage', 'Web Page'),
('presentation', 'Presentation'),
('document', 'Document'),
- ('webpage', 'Web Page'),
- ('video', 'Video')],
+ ('video', 'Video'),
+ ('quiz', "Quiz")],
string='Type', required=True,
default='document', readonly=True,
help="The document type will be set automatically based on the document URL and properties (e.g. height and width for presentation and document).")