From 98bdb241ded7beb4a2d958387ad2d5ec620feb3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= <tde@odoo.com> Date: Thu, 25 Jul 2019 10:50:35 +0000 Subject: [PATCH] [IMP] website_slides: add an overview (kanban view) on course model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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> --- addons/website_slides/__manifest__.py | 1 + .../models/res_config_settings.py | 4 +- addons/website_slides/models/slide_channel.py | 64 +++++++++++-- .../static/src/scss/slide_channel_views.scss | 7 ++ addons/website_slides/views/assets.xml | 1 + .../views/res_config_settings_views.xml | 6 +- .../views/res_partner_views.xml | 5 + .../views/slide_channel_views.xml | 96 ++++++++++++++++++- .../views/slide_channel_views.xml | 15 +++ 9 files changed, 182 insertions(+), 17 deletions(-) create mode 100644 addons/website_slides/static/src/scss/slide_channel_views.scss diff --git a/addons/website_slides/__manifest__.py b/addons/website_slides/__manifest__.py index 5373f5bd6de9..1da8adc4eeed 100644 --- a/addons/website_slides/__manifest__.py +++ b/addons/website_slides/__manifest__.py @@ -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', diff --git a/addons/website_slides/models/res_config_settings.py b/addons/website_slides/models/res_config_settings.py index fa9e7508afcb..8085cd407c8f 100644 --- a/addons/website_slides/models/res_config_settings.py +++ b/addons/website_slides/models/res_config_settings.py @@ -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") diff --git a/addons/website_slides/models/slide_channel.py b/addons/website_slides/models/slide_channel.py index 4496c59b391c..78c32b55b741 100644 --- a/addons/website_slides/models/slide_channel.py +++ b/addons/website_slides/models/slide_channel.py @@ -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_channel_invite(self): - self.ensure_one() + def action_redirect_to_running_members(self): + return self.action_redirect_to_members('running') - if self.enroll != 'invite': - raise UserError(_("You cannot send invitations for channels that are not set as 'invite'.")) + def action_redirect_to_done_members(self): + return self.action_redirect_to_members('completed') + def action_channel_invite(self): + self.ensure_one() 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 # --------------------------------------------------------- diff --git a/addons/website_slides/static/src/scss/slide_channel_views.scss b/addons/website_slides/static/src/scss/slide_channel_views.scss new file mode 100644 index 000000000000..3bd8203c4eb0 --- /dev/null +++ b/addons/website_slides/static/src/scss/slide_channel_views.scss @@ -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 + } +} diff --git a/addons/website_slides/views/assets.xml b/addons/website_slides/views/assets.xml index 1c990e7ad502..62129e58e6bf 100644 --- a/addons/website_slides/views/assets.xml +++ b/addons/website_slides/views/assets.xml @@ -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> diff --git a/addons/website_slides/views/res_config_settings_views.xml b/addons/website_slides/views/res_config_settings_views.xml index 6925fbc33b58..3e5a64010f05 100644 --- a/addons/website_slides/views/res_config_settings_views.xml +++ b/addons/website_slides/views/res_config_settings_views.xml @@ -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> diff --git a/addons/website_slides/views/res_partner_views.xml b/addons/website_slides/views/res_partner_views.xml index a5e7161c00b5..5455a0195f5e 100644 --- a/addons/website_slides/views/res_partner_views.xml +++ b/addons/website_slides/views/res_partner_views.xml @@ -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> diff --git a/addons/website_slides/views/slide_channel_views.xml b/addons/website_slides/views/slide_channel_views.xml index edd662541e57..898599ede254 100644 --- a/addons/website_slides/views/slide_channel_views.xml +++ b/addons/website_slides/views/slide_channel_views.xml @@ -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 diff --git a/addons/website_slides_survey/views/slide_channel_views.xml b/addons/website_slides_survey/views/slide_channel_views.xml index e28b67997cf0..2b93ef0dd2af 100644 --- a/addons/website_slides_survey/views/slide_channel_views.xml +++ b/addons/website_slides_survey/views/slide_channel_views.xml @@ -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> -- GitLab