[IMP] website_slides: allow course to have prerequisites

This commit allows the course manager to add prerequisites
courses to a course so that the final user will be notified
on the frontend he has to take some courses before the one he
wants. He could however request access to the course and
take it without taking the prerequisites before if the access
has been granted.

Task-3213628

closes odoo/odoo#114710

Signed-off-by: Warnon Aurélien (awa) <awa@odoo.com>
This commit is contained in:
Patrick Hoste
2023-05-03 16:19:55 +02:00
parent 8272b1ff0a
commit 39fdb8064c
9 changed files with 157 additions and 32 deletions
@@ -125,6 +125,23 @@
<span t-field="channel.product_id.list_price" t-options="{'widget': 'monetary', 'display_currency': product_info['currency_id']}"/>
</div>
</div>
<t t-if="channel.prerequisite_channel_ids and not channel.prerequisite_user_has_completed">
<small t-if="len(channel.prerequisite_channel_ids) == 1" class="text-center">
Prerequisite:
<a t-attf-href="/slides/{{slug(channel.prerequisite_channel_ids[0])}}"
t-out="channel.prerequisite_channel_ids[0].name"/>
</small>
<small t-else="" class="text-center">
There are some
<a href="#" class="o_wslides_js_prerequisite_course"
t-att-data-channels="json.dumps([
{'course_id': course.id, 'course_name': course.name}
for course in channel.prerequisite_channel_ids]
)">
prerequisite courses.
</a>
</small>
</t>
<div class="oe_website_sale">
<div class="add_to_cart_button">
<form action="/shop/cart/update" method="POST">
+2
View File
@@ -95,6 +95,7 @@ Featuring
'website_slides/static/src/js/slides_course_fullscreen_player.js',
'website_slides/static/src/js/slides_course_join.js',
'website_slides/static/src/js/slides_course_enroll_email.js',
'website_slides/static/src/js/slides_course_prerequisite.js',
'website_slides/static/src/js/slides_course_quiz.js',
'website_slides/static/src/js/slides_course_quiz_question_form.js',
'website_slides/static/src/js/slides_course_quiz_finish.js',
@@ -109,6 +110,7 @@ Featuring
'website_slides/static/src/xml/website_slides_unsubscribe.xml',
'website_slides/static/src/xml/slide_management.xml',
'website_slides/static/src/xml/slide_course_join.xml',
'website_slides/static/src/xml/slide_course_prerequisite.xml',
'website_slides/static/src/xml/slide_quiz_create.xml',
'website_slides/static/src/xml/slide_quiz.xml',
],
@@ -42,6 +42,9 @@
<record id="slide_channel_demo_2_gard2" model="slide.channel">
<field name="name">Trees, Wood and Gardens</field>
<field name="user_id" ref="base.user_demo"/>
<field name="prerequisite_channel_ids" eval="[(5, 0),
(4, ref('website_slides.slide_channel_demo_0_gard_0')),
(4, ref('website_slides.slide_channel_demo_1_gard1'))]"/>
<field name="enroll">public</field>
<field name="channel_type">documentation</field>
<field name="allow_comment" eval="True"/>
@@ -359,6 +359,16 @@ class Channel(models.Model):
can_review = fields.Boolean('Can Review', compute='_compute_action_rights', compute_sudo=False)
can_comment = fields.Boolean('Can Comment', compute='_compute_action_rights', compute_sudo=False)
can_vote = fields.Boolean('Can Vote', compute='_compute_action_rights', compute_sudo=False)
# prerequisite settings
prerequisite_channel_ids = fields.Many2many(
'slide.channel', 'slide_channel_prerequisite_slide_channel_rel', 'channel_id', 'prerequisite_channel_id',
string='Prerequisites', help='Prerequisite courses to complete before accessing this one.',
domain="[('id', '!=', id), ('visibility', '=', visibility), ('website_published', '=', website_published)]")
prerequisite_of_channel_ids = fields.Many2many(
'slide.channel', 'slide_channel_prerequisite_slide_channel_rel', 'prerequisite_channel_id', 'channel_id',
string='Prerequisite Of', help='Courses that have this course as prerequisite.')
prerequisite_user_has_completed = fields.Boolean(
'Has Completed Prerequisite', compute='_compute_prerequisite_user_has_completed')
_sql_constraints = [
(
@@ -540,6 +550,22 @@ class Channel(models.Model):
channel.can_comment = user_karma >= channel.karma_slide_comment
channel.can_vote = user_karma >= channel.karma_slide_vote
######################
# Prerequisite Compute
######################
@api.depends('prerequisite_channel_ids', 'channel_partner_ids.completed')
@api.depends_context('uid')
def _compute_prerequisite_user_has_completed(self):
completed_prerequisite_channels = self.env['slide.channel.partner'].sudo().search([
('partner_id', '=', self.env.user.partner_id.id),
('channel_id', 'in', self.prerequisite_channel_ids.ids),
('completed', '=', True),
]).mapped('channel_id')
for channel in self:
channel.prerequisite_user_has_completed = all(
channel in completed_prerequisite_channels for channel in channel.prerequisite_channel_ids)
# ---------------------------------------------------------
# ORM Overrides
# ---------------------------------------------------------
@@ -58,7 +58,7 @@ publicWidget.registry.websiteSlidesEnroll = publicWidget.Widget.extend({
_openDialog: function (channelId) {
new SlideEnrollDialog(this, {
channelId: channelId,
$element: this.$el
$element: this.$el.closest('.alert')
}).open();
},
@@ -0,0 +1,24 @@
/** @odoo-module **/
import { qweb } from 'web.core';
import publicWidget from 'web.public.widget';
publicWidget.registry.websiteSlidesCoursePrerequisite = publicWidget.Widget.extend({
selector: '.o_wslides_js_prerequisite_course',
async start() {
await this._super(...arguments);
const channels = this.$el.data('channels');
this.$el.popover({
trigger: 'focus',
placement: 'bottom',
container: 'body',
html: true,
content: qweb.render('slide.course.prerequisite', {channels: channels}),
});
},
});
export default {
websiteSlidesCoursePrerequisite: publicWidget.registry.websiteSlidesCoursePrerequisite
};
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-name="slide.course.prerequisite">
<ul class="m-0 ps-3">
<li t-foreach="channels" t-as="channel" t-key="channel.course_id">
<a t-attf-href="/slides/#{channel.course_id}" t-out="channel.course_name"/>
</li>
</ul>
</t>
</templates>
@@ -98,6 +98,10 @@
<field name="website_id" options="{'no_create': True}" groups="website.group_multi_website"/>
</group>
<group name="access_rights" string="Access Rights">
<field name="website_published" invisible="1"/>
<field name="prerequisite_channel_ids" widget="many2many_tags"/>
<field name="prerequisite_of_channel_ids" widget="many2many_tags" readonly="1"
attrs="{'invisible': [('prerequisite_of_channel_ids', '=', [])]}"/>
<field name="visibility" widget="radio" options="{'horizontal': true}"/>
<field name="enroll" widget="radio" options="{'horizontal': true}" attrs="{'invisible': [('visibility', '=', 'course_attendees')]}"/>
<field name="upload_group_ids" widget="many2many_tags" groups="base.group_no_one"/>
@@ -275,37 +275,6 @@
<template id="course_join">
<div class="o_wslides_js_course_join flex-grow-1 d-grid">
<a t-if="not channel.is_member and channel.enroll == 'public'" role="button"
class="btn btn-primary o_wslides_js_course_join_link"
title="Start Course" aria-label="Start Course Channel" href="#"
t-att-data-channel-id="channel.id"
t-att-data-channel-enroll="channel.enroll">
<span class="cta-title text_small_caps">
Join this Course
</span>
</a>
<div t-if="not channel.is_member and channel.enroll == 'invite'" class="text-center">
<div t-attf-class="alert my-0 bg-100 p-2 #{'o_wslides_js_channel_enroll' if not is_public_user else ''}"
t-att-data-channel-id="channel.id">
Private Course
<div t-if="is_public_user">
<small>
Please <a t-att-href="'/web/login?redirect=/slides/%s' % (slug(channel))">sign in</a> to contact responsible.
</small>
</div>
<div t-elif="channel.has_requested_access">
<small class="text-success">
Request already sent
</small>
</div>
<div t-else="" class="o_wslides_enroll_msg">
<small>
<div t-field="channel.enroll_msg"/>
</small>
</div>
</div>
</div>
<t t-if="channel.is_member">
<button class="d-flex align-items-center alert my-0 px-2 px-xl-3 bg-100 w-100 o_wslides_js_channel_unsubscribe"
t-att-data-channel-id="channel.id"
@@ -333,6 +302,76 @@
</div>
</div>
</t>
<t t-elif="channel.enroll in ['public', 'invite']">
<div t-if="channel.prerequisite_channel_ids and not channel.prerequisite_user_has_completed"
class="text-center">
<div class="alert my-0 bg-100">
<h6>
<i class="fa fa-lock"/>
<span>Course Locked</span>
</h6>
<div>
<small>
Finish
<a t-if="len(channel.prerequisite_channel_ids) == 1"
t-attf-href="/slides/{{slug(channel.prerequisite_channel_ids[0])}}"
t-out="channel.prerequisite_channel_ids[0].name"/>
<a t-else="" href="#" class="o_wslides_js_prerequisite_course"
t-att-data-channels="json.dumps(
[{'course_id': course.id, 'course_name': course.name}
for course in channel.prerequisite_channel_ids]
)">
courses
</a>
to unlock
</small>
</div>
<div>
<small t-if="channel.enroll == 'public'">
or
<a href="#" class="o_wslides_js_course_join_link"
t-att-data-channel-id="channel.id"
t-att-data-channel-enroll="channel.enroll">
join
</a> anyway.
</small>
<small t-else="">
or <a href="#" class="o_wslides_js_channel_enroll" t-att-data-channel-id="channel.id">request</a> direct access.
</small>
</div>
</div>
</div>
<t t-else="">
<a t-if="channel.enroll == 'public'" role="button"
class="btn btn-primary o_wslides_js_course_join_link"
title="Start Course" aria-label="Start Course Channel" href="#"
t-att-data-channel-id="channel.id"
t-att-data-channel-enroll="channel.enroll">
<span class="cta-title text_small_caps">
Join this Course
</span>
</a>
<div t-if="channel.enroll == 'invite'" class="text-center">
<div t-attf-class="alert my-0 bg-100 p-2 #{'o_wslides_js_channel_enroll' if not is_public_user else ''}"
t-att-data-channel-id="channel.id">
Private Course
<div t-if="is_public_user">
<small>
Please <a t-attf-href="/web/login?redirect=/slides/{{slug(channel)}}">sign in</a> to contact responsible.
</small>
</div>
<div t-elif="channel.has_requested_access">
<small class="text-success">
Request already sent
</small>
</div>
<div t-else="" class="o_wslides_enroll_msg">
<small t-field="channel.enroll_msg"/>
</div>
</div>
</div>
</t>
</t>
</div>
</template>