[IMP] website_slides: add an overview (kanban view) on course model
PURPOSE
eLearning should have its own application in manager. Indeed this is becoming
a big application and having it embedded inside Website application is not
enough anymore. It should also hold updated and easy-to-use menus,
navigation, actions and views in backend.
SPECIFICATIONS
Add course (slide.channel) kanban view. Each tile should contain
* Title of the course (make it big, o_primary)
* Tags below the title
* Primary button "New Lesson"
* Statistics :
* Attendees
* Running (progress != 100%)
* (if one content is certificate) Certified or Finished
* See
* v1: https://drive.google.com/a/odoo.com/file/d/13K8FmkbwmBG4JQ5gH3Q5T8OlWPs3blcu/view?usp=drivesdk
With FP remarks https://drive.google.com/a/odoo.com/file/d/1mb7LUT0VUNs5fMcKB9c0Bfaqk2-XFFuu/view?usp=drivesdk
* remove globe, type and visibility of course, lessen padding between rating
views and watch time;
* for rating, instead of 8.00 => 5 reviews (4.5/5);
* replace "View Lesson" / "Invite" by "View Course" => Send to the front-end
* In the menu of the card, add:
* Invite (same behavior than before, when it was a primary btn)
* click on x Contents: land on the contents, group by category with sequence
* re-order of the 3 infos/Stats at the bottom of the card: 1. Content,
2. Attendee, 3. Certified
* + number: font-weight: 500 + font-size 1.2em
* + labels: remove text-uppercase, + text-color: #666666
* + the "whole box" is clickable
LINKS
Task 1978729
PR #35061
Co-Authored-By: Thibault Delavallée <tde@odoo.com>
Co-Authored-By: Jérémy Hennecart <jeh@odoo.com>
This commit is contained in:
co-authored by
Jérémy Hennecart
parent
665d0dfa67
commit
98bdb241de
@@ -29,6 +29,7 @@ Featuring
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/res_partner_views.xml',
|
||||
'views/rating_rating_views.xml',
|
||||
'views/res_partner_views.xml',
|
||||
'views/slide_question_views.xml',
|
||||
'views/slide_slide_views.xml',
|
||||
'views/slide_channel_partner_views.xml',
|
||||
|
||||
@@ -8,6 +8,6 @@ class ResConfigSettings(models.TransientModel):
|
||||
_inherit = "res.config.settings"
|
||||
|
||||
website_slide_google_app_key = fields.Char(related='website_id.website_slide_google_app_key', readonly=False)
|
||||
module_website_sale_slides = fields.Boolean(string="Sell courses")
|
||||
module_website_slides_forum = fields.Boolean(string="Forum on Courses")
|
||||
module_website_sale_slides = fields.Boolean(string="Sell on eCommerce")
|
||||
module_website_slides_forum = fields.Boolean(string="Forum")
|
||||
module_website_slides_survey = fields.Boolean(string="Certifications")
|
||||
|
||||
@@ -73,6 +73,7 @@ class ChannelUsersRelation(models.Model):
|
||||
def _post_completion_hook(self):
|
||||
pass
|
||||
|
||||
|
||||
class Channel(models.Model):
|
||||
""" A channel is a container of slides. """
|
||||
_name = 'slide.channel'
|
||||
@@ -96,6 +97,7 @@ class Channel(models.Model):
|
||||
string="Course type", default="documentation", required=True)
|
||||
sequence = fields.Integer(default=10, help='Display order')
|
||||
user_id = fields.Many2one('res.users', string='Responsible', default=lambda self: self.env.uid)
|
||||
color = fields.Integer('Color Index', default=0, help='Used to decorate kanban view')
|
||||
tag_ids = fields.Many2many(
|
||||
'slide.channel.tag', 'slide_channel_tag_rel', 'channel_id', 'tag_id',
|
||||
string='Tags', help='Used to categorize and filter displayed channels/courses')
|
||||
@@ -151,6 +153,7 @@ class Channel(models.Model):
|
||||
'res.partner', 'slide_channel_partner', 'channel_id', 'partner_id',
|
||||
string='Members', help="All members of the channel.", context={'active_test': False})
|
||||
members_count = fields.Integer('Attendees count', compute='_compute_members_count')
|
||||
members_done_count = fields.Integer('Attendees Done Count', compute='_compute_members_done_count')
|
||||
is_member = fields.Boolean(string='Is Member', compute='_compute_is_member')
|
||||
channel_partner_ids = fields.One2many('slide.channel.partner', 'channel_id', string='Members Information', groups='website.group_website_publisher')
|
||||
upload_group_ids = fields.Many2many(
|
||||
@@ -184,6 +187,13 @@ class Channel(models.Model):
|
||||
for channel in self:
|
||||
channel.members_count = data.get(channel.id, 0)
|
||||
|
||||
@api.depends('channel_partner_ids.channel_id', 'channel_partner_ids.completed')
|
||||
def _compute_members_done_count(self):
|
||||
read_group_res = self.env['slide.channel.partner'].sudo().read_group(['&', ('channel_id', 'in', self.ids), ('completed', '=', True)], ['channel_id'], 'channel_id')
|
||||
data = dict((res['channel_id'][0], res['channel_id_count']) for res in read_group_res)
|
||||
for channel in self:
|
||||
channel.members_done_count = data.get(channel.id, 0)
|
||||
|
||||
@api.depends('channel_partner_ids.partner_id')
|
||||
@api.model
|
||||
def _compute_is_member(self):
|
||||
@@ -366,21 +376,34 @@ class Channel(models.Model):
|
||||
# Business / Actions
|
||||
# ---------------------------------------------------------
|
||||
|
||||
def action_redirect_to_members(self):
|
||||
action = self.env.ref('website_slides.slide_channel_partner_action').read()[0]
|
||||
action['view_mode'] = 'tree'
|
||||
action['domain'] = [('channel_id', 'in', self.ids)]
|
||||
def action_redirect_to_members(self, state=None):
|
||||
action = self.env.ref('website_slides.res_partner_action_slide_channel').read()[0]
|
||||
action['context'] = {'active_test': False}
|
||||
if state == 'running':
|
||||
action['domain'] = [('id', 'in', self.env['slide.channel.partner'].sudo().search([
|
||||
('channel_id', 'in', self.ids),
|
||||
('completed', '=', False)
|
||||
]).mapped('partner_id').ids)]
|
||||
elif state == 'completed':
|
||||
action['domain'] = [('id', 'in', self.env['slide.channel.partner'].sudo().search([
|
||||
('channel_id', 'in', self.ids),
|
||||
('completed', '=', True)
|
||||
]).mapped('partner_id').ids)]
|
||||
else:
|
||||
action['domain'] = [('id', 'in', self.mapped('partner_ids').ids)]
|
||||
if len(self) == 1:
|
||||
action['context'] = {'default_channel_id': self.id}
|
||||
|
||||
action['display_name'] = _('Attendees of %s') % self.name
|
||||
action['context'] = {'active_test': False, 'default_channel_id': self.id}
|
||||
return action
|
||||
|
||||
def action_redirect_to_running_members(self):
|
||||
return self.action_redirect_to_members('running')
|
||||
|
||||
def action_redirect_to_done_members(self):
|
||||
return self.action_redirect_to_members('completed')
|
||||
|
||||
def action_channel_invite(self):
|
||||
self.ensure_one()
|
||||
|
||||
if self.enroll != 'invite':
|
||||
raise UserError(_("You cannot send invitations for channels that are not set as 'invite'."))
|
||||
|
||||
template = self.env.ref('website_slides.mail_template_slide_channel_invite', raise_if_not_found=False)
|
||||
|
||||
local_context = dict(
|
||||
@@ -474,6 +497,27 @@ class Channel(models.Model):
|
||||
if removed_channel_partner_domain:
|
||||
self.env['slide.channel.partner'].sudo().search(removed_channel_partner_domain).unlink()
|
||||
|
||||
def action_view(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'slide.channel',
|
||||
'res_id': self.id,
|
||||
}
|
||||
|
||||
def action_view_slides(self):
|
||||
action = self.env.ref('website_slides.slide_slide_action').read()[0]
|
||||
action['context'] = {'default_channel_id': self.id}
|
||||
action['domain'] = [('channel_id', "=", self.id)]
|
||||
return action
|
||||
|
||||
def action_view_ratings(self):
|
||||
action = self.env.ref('website_slides.rating_rating_action_slide_channel').read()[0]
|
||||
action['name'] = _('Rating of %s') % (self.name)
|
||||
action['domain'] = [('res_id', 'in', self.ids)]
|
||||
return action
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Rating Mixin API
|
||||
# ---------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
$o-kanban-large-record-width: 400px;
|
||||
|
||||
.o_kanban_view.o_slide_channel_kanban {
|
||||
.o_kanban_record {
|
||||
width: $o-kanban-large-record-width
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
<template id="assets_backend" name="Slides Backend Assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/rating_rating_views.scss"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/slide_channel_views.scss"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="module_website_slides_forum"/>
|
||||
<div class="text-muted">
|
||||
Allow Forum on Courses
|
||||
Create a community and let the members help each others
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -50,7 +50,7 @@
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="module_website_slides_survey"/>
|
||||
<div class="text-muted">
|
||||
Allow to take certifications
|
||||
Evaluate your students and certify them
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -62,7 +62,7 @@
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="module_website_sale_slides"/>
|
||||
<div class="text-muted">
|
||||
Sell courses on your website
|
||||
Generate revenues thanks to your courses
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -36,4 +36,9 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="res_partner_action_slide_channel" model="ir.actions.act_window">
|
||||
<field name="name">Attendees</field>
|
||||
<field name="res_model">res.partner</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
</record>
|
||||
</data></odoo>
|
||||
|
||||
@@ -218,11 +218,103 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="slide_channel_view_kanban" model="ir.ui.view">
|
||||
<field name="name">slide.channel.view.kanban</field>
|
||||
<field name="model">slide.channel</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban string="eLearning Overview" class="o_emphasize_colors o_kanban_dashboard o_slide_channel_kanban breadcrumb_item active" edit="false">
|
||||
<field name="color"/>
|
||||
<field name="website_published"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click">
|
||||
<div class="o_dropdown_kanban dropdown">
|
||||
<a role="button" class="dropdown-toggle o-no-caret btn" data-toggle="dropdown" href="#" aria-label="Dropdown menu" title="Dropdown menu">
|
||||
<span class="fa fa-ellipsis-v" aria-hidden="false"/>
|
||||
</a>
|
||||
<div class="dropdown-menu" role="menu">
|
||||
<ul class="oe_kanban_colorpicker" data-field="color"/>
|
||||
<t t-if="widget.deletable">
|
||||
<a class="dropdown-item" role="menuitem" type="delete">Delete</a>
|
||||
</t>
|
||||
<a class="dropdown-item" role="menuitem" type="edit">
|
||||
Edit
|
||||
</a>
|
||||
<a class="dropdown-item" name="action_view_slides" role="menuitem" type="object">
|
||||
Lessons
|
||||
</a>
|
||||
<a class="dropdown-item" name="action_channel_invite" role="menuitem" type="object">
|
||||
Invite
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_kanban_card_header">
|
||||
<div class="o_kanban_card_header_title mb16">
|
||||
<div class="o_primary">
|
||||
<a name="action_view" type="object" class="mr-auto">
|
||||
<span><field name="name" class="o_primary"/></span>
|
||||
</a>
|
||||
</div>
|
||||
<div t-if="record.tag_ids">
|
||||
<field name="tag_ids" widget="many2many_tags"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container o_kanban_card_content mt0">
|
||||
<div class="row">
|
||||
<div class="col-6 o_kanban_primary_left">
|
||||
<button class="btn btn-primary" name="open_website_url" type="object">View course</button>
|
||||
</div>
|
||||
<div class="col-6 o_kanban_primary_right">
|
||||
<div class="d-flex">
|
||||
<span class="mr-auto"><label for="rating_avg" class="mb0">Rating</label></span>
|
||||
<a name="action_view_ratings" type="object">
|
||||
<field name="rating_count"/><span class="ml-2">reviews</span> (<field name="rating_avg_stars"/>/5)
|
||||
</a>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<span class="mr-auto"><label for="total_views" class="mb0">Views</label></span>
|
||||
<field name="total_views"/>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<span class="mr-auto"><label for="total_time" class="mb0">Watch Time</label></span>
|
||||
<field name="total_time" widget="float_time"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt3">
|
||||
<div class="col-4 border-right">
|
||||
<a name="action_view_slides" type="object" class="d-flex flex-column align-items-center">
|
||||
<span class="font-weight-bold"><field name="total_slides"/></span>
|
||||
<span class="text-secondary">Contents</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-4 border-right">
|
||||
<a name="action_redirect_to_members" type="object" class="d-flex flex-column align-items-center">
|
||||
<span class="font-weight-bold"><field name="members_count"/></span>
|
||||
<span class="text-secondary">Attendees</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<a name="action_redirect_to_done_members" type="object" class="d-flex flex-column align-items-center">
|
||||
<span class="font-weight-bold"><field name="members_done_count"/></span>
|
||||
<span class="text-secondary">Finished</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="slide_channel_action_overview" model="ir.actions.act_window">
|
||||
<field name="name">eLearning Overview</field>
|
||||
<field name="res_model">slide.channel</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="slide_channel_view_tree"/>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="view_id" ref="slide_channel_view_kanban"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create a course
|
||||
|
||||
@@ -10,4 +10,19 @@
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="slide_channel_view_kanban" model="ir.ui.view">
|
||||
<field name="name">slide.channel.view.kanban.inherit.survey</field>
|
||||
<field name="model">slide.channel</field>
|
||||
<field name="inherit_id" ref="website_slides.slide_channel_view_kanban"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='members_running_count']" position="after">
|
||||
<field name="nbr_certification"/>
|
||||
</xpath>
|
||||
<xpath expr="//a[@name='action_redirect_to_done_members']/span" position="replace">
|
||||
<t t-if="record.nbr_certification.raw_value"><span class="text-uppercase">Certified</span></t>
|
||||
<t t-else=""><span class="text-uppercase">Finished</span></t>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
Reference in New Issue
Block a user