[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:
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user