From a91972a9109b1ef150836ad795330cb9df566c99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 24 Aug 2020 12:00:22 +0000 Subject: [PATCH 1/3] [FIX] website_slides: set groups on slide-related fields of partner model --- addons/website_slides/models/res_partner.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/addons/website_slides/models/res_partner.py b/addons/website_slides/models/res_partner.py index a36b23227c8..7f3aaaf9ed2 100644 --- a/addons/website_slides/models/res_partner.py +++ b/addons/website_slides/models/res_partner.py @@ -9,9 +9,13 @@ class ResPartner(models.Model): slide_channel_ids = fields.Many2many( 'slide.channel', 'slide_channel_partner', 'partner_id', 'channel_id', - string='eLearning Courses') - slide_channel_count = fields.Integer('Course Count', compute='_compute_slide_channel_count') - slide_channel_company_count = fields.Integer('Company Course Count', compute='_compute_slide_channel_company_count') + string='eLearning Courses', groups="website_slides.group_website_slides_officer") + slide_channel_count = fields.Integer( + 'Course Count', compute='_compute_slide_channel_count', + groups="website_slides.group_website_slides_officer") + slide_channel_company_count = fields.Integer( + 'Company Course Count', compute='_compute_slide_channel_company_count', + groups="website_slides.group_website_slides_officer") @api.depends('is_company') def _compute_slide_channel_count(self): From 0b43747a4bdf549e5f4770662b5db523ebc04e89 Mon Sep 17 00:00:00 2001 From: Patrick Hoste Date: Wed, 17 Jun 2020 13:21:50 +0000 Subject: [PATCH 2/3] [IMP] website_slides: add partner completed course filter PURPOSE Before this commit, it was possible to send an email to all attendees only. After this commit, it will be possible to send an email to attendees that completed the course. SPECIFICATION Adds a searchable o2m field in the res.partner model to find all the completed courses linked to that partner. LINKS Task ID : 2216352 PR : #49592 --- .../views/slide_channel_views.xml | 26 +++++++------- addons/website_slides/models/res_partner.py | 14 ++++++++ .../static/src/scss/slide_views.scss | 5 +++ .../views/slide_channel_views.xml | 34 +++++++++++-------- 4 files changed, 51 insertions(+), 28 deletions(-) diff --git a/addons/mass_mailing_slides/views/slide_channel_views.xml b/addons/mass_mailing_slides/views/slide_channel_views.xml index 9b7f35b202c..66265fa222c 100644 --- a/addons/mass_mailing_slides/views/slide_channel_views.xml +++ b/addons/mass_mailing_slides/views/slide_channel_views.xml @@ -5,16 +5,11 @@ slide.channel - - - + @@ -23,10 +18,13 @@ slide.channel - - - Mail Attendees - + + diff --git a/addons/website_slides/models/res_partner.py b/addons/website_slides/models/res_partner.py index 7f3aaaf9ed2..541947c9035 100644 --- a/addons/website_slides/models/res_partner.py +++ b/addons/website_slides/models/res_partner.py @@ -10,6 +10,9 @@ class ResPartner(models.Model): slide_channel_ids = fields.Many2many( 'slide.channel', 'slide_channel_partner', 'partner_id', 'channel_id', string='eLearning Courses', groups="website_slides.group_website_slides_officer") + slide_channel_completed_ids = fields.One2many( + 'slide.channel', string='Completed Courses', compute='_compute_slide_channel_completed_ids', + search='_search_slide_channel_completed_ids') slide_channel_count = fields.Integer( 'Course Count', compute='_compute_slide_channel_count', groups="website_slides.group_website_slides_officer") @@ -17,6 +20,17 @@ class ResPartner(models.Model): 'Company Course Count', compute='_compute_slide_channel_company_count', groups="website_slides.group_website_slides_officer") + def _compute_slide_channel_completed_ids(self): + for partner in self: + partner.slide_channel_completed_ids = self.env['slide.channel.partner'].search([ + ('partner_id', '=', partner.id), + ('completed', '=', True) + ]).mapped('channel_id') + + def _search_slide_channel_completed_ids(self, operator, value): + cp_done = self.env['slide.channel.partner'].search([('channel_id', operator, value), ('completed', '=', True)]) + return [('id', 'in', cp_done.partner_id.ids)] + @api.depends('is_company') def _compute_slide_channel_count(self): read_group_res = self.env['slide.channel.partner'].sudo().read_group( diff --git a/addons/website_slides/static/src/scss/slide_views.scss b/addons/website_slides/static/src/scss/slide_views.scss index 035f4699309..6afd73e9423 100644 --- a/addons/website_slides/static/src/scss/slide_views.scss +++ b/addons/website_slides/static/src/scss/slide_views.scss @@ -7,6 +7,11 @@ $o-kanban-large-record-width: 400px; .o_kanban_record { width: $o-kanban-large-record-width; } + .o_kanban_slides_card_manage_pane { + > div { + @include o-kanban-dashboard-dropdown-link; + } + } } table.o_section_list_view tr.o_data_row.o_is_section { diff --git a/addons/website_slides/views/slide_channel_views.xml b/addons/website_slides/views/slide_channel_views.xml index f207e36a50c..9637ffd2786 100644 --- a/addons/website_slides/views/slide_channel_views.xml +++ b/addons/website_slides/views/slide_channel_views.xml @@ -214,20 +214,26 @@ -
From 375e38fcd8c489ae9c1f1f09854cd34e762c355d Mon Sep 17 00:00:00 2001 From: Patrick Hoste Date: Wed, 17 Jun 2020 13:22:57 +0000 Subject: [PATCH 3/3] [IMP] website_slides: send an email when an attendee finishes a course PURPOSE After this commit, an email will be sent to congratulate the attendee for the completion of a course. SPECIFICATION Adds a new 'Completion Email' template field in the slide.channel model that will be sent automatically after an attendee completes a course. LINKS Task ID : 2216352 PR : #49592 --- addons/website_slides/__manifest__.py | 2 +- addons/website_slides/data/mail_data.xml | 32 ++++++++ addons/website_slides/models/res_partner.py | 11 ++- addons/website_slides/models/slide_channel.py | 75 ++++++++++++++++--- .../views/slide_channel_partner_views.xml | 8 +- .../views/slide_channel_views.xml | 3 +- 6 files changed, 108 insertions(+), 23 deletions(-) diff --git a/addons/website_slides/__manifest__.py b/addons/website_slides/__manifest__.py index 2c3acef508f..d16c423565c 100644 --- a/addons/website_slides/__manifest__.py +++ b/addons/website_slides/__manifest__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- { 'name': 'eLearning', - 'version': '2.2', + 'version': '2.3', 'sequence': 125, 'summary': 'Manage and publish an eLearning platform', 'website': 'https://www.odoo.com/page/slides', diff --git a/addons/website_slides/data/mail_data.xml b/addons/website_slides/data/mail_data.xml index f7fa34203ee..93b3a996cf6 100644 --- a/addons/website_slides/data/mail_data.xml +++ b/addons/website_slides/data/mail_data.xml @@ -63,6 +63,38 @@ + + + Completed Course + + Congratulation! You completed ${object.channel_id.name} + ${(object.channel_id.user_id.email_formatted or object.channel_id.user_id.company_id.catchall_formatted) | safe} + ${object.partner_id.id} + +
+
+

Hello ${object.partner_id.name},


+

Congratulations!

+

You've completed the course ${object.channel_id.name}

+

Check out the other available courses.


+ + + Enjoy this exclusive content! + % if object.channel_id.user_id.signature +
+ ${object.channel_id.user_id.signature | safe} + % endif +
+
+
+ + ${object.partner_id.lang} +
+ Presentation Published diff --git a/addons/website_slides/models/res_partner.py b/addons/website_slides/models/res_partner.py index 541947c9035..54fd52e96d4 100644 --- a/addons/website_slides/models/res_partner.py +++ b/addons/website_slides/models/res_partner.py @@ -11,8 +11,10 @@ class ResPartner(models.Model): 'slide.channel', 'slide_channel_partner', 'partner_id', 'channel_id', string='eLearning Courses', groups="website_slides.group_website_slides_officer") slide_channel_completed_ids = fields.One2many( - 'slide.channel', string='Completed Courses', compute='_compute_slide_channel_completed_ids', - search='_search_slide_channel_completed_ids') + 'slide.channel', string='Completed Courses', + compute='_compute_slide_channel_completed_ids', + search='_search_slide_channel_completed_ids', + groups="website_slides.group_website_slides_officer") slide_channel_count = fields.Integer( 'Course Count', compute='_compute_slide_channel_count', groups="website_slides.group_website_slides_officer") @@ -28,7 +30,10 @@ class ResPartner(models.Model): ]).mapped('channel_id') def _search_slide_channel_completed_ids(self, operator, value): - cp_done = self.env['slide.channel.partner'].search([('channel_id', operator, value), ('completed', '=', True)]) + cp_done = self.env['slide.channel.partner'].sudo().search([ + ('channel_id', operator, value), + ('completed', '=', True) + ]) return [('id', 'in', cp_done.partner_id.ids)] @api.depends('is_company') diff --git a/addons/website_slides/models/slide_channel.py b/addons/website_slides/models/slide_channel.py index 1479a51722d..890863155c7 100644 --- a/addons/website_slides/models/slide_channel.py +++ b/addons/website_slides/models/slide_channel.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +import logging import uuid from collections import defaultdict @@ -11,6 +12,8 @@ from odoo.addons.http_routing.models.ir_http import slug from odoo.exceptions import AccessError from odoo.osv import expression +_logger = logging.getLogger(__name__) + class ChannelUsersRelation(models.Model): _name = 'slide.channel.partner' @@ -38,21 +41,16 @@ class ChannelUsersRelation(models.Model): mapped_data.setdefault(item['channel_id'][0], dict()) mapped_data[item['channel_id'][0]][item['partner_id'][0]] = item['__count'] - partner_karma = dict.fromkeys(self.mapped('partner_id').ids, 0) + completed_records = self.env['slide.channel.partner'] for record in self: record.completed_slides_count = mapped_data.get(record.channel_id.id, dict()).get(record.partner_id.id, 0) record.completion = 100.0 if record.completed else round(100.0 * record.completed_slides_count / (record.channel_id.total_slides or 1)) if not record.completed and record.completed_slides_count >= record.channel_id.total_slides: - record.completed = True - partner_karma[record.partner_id.id] += record.channel_id.karma_gen_channel_finish + completed_records += record - partner_karma = {partner_id: karma_to_add - for partner_id, karma_to_add in partner_karma.items() if karma_to_add > 0} - - if partner_karma: - users = self.env['res.users'].sudo().search([('partner_id', 'in', list(partner_karma.keys()))]) - for user in users: - users.add_karma(partner_karma[user.partner_id.id]) + if completed_records: + completed_records._set_as_completed() + completed_records._send_completed_mail() def unlink(self): """ @@ -71,6 +69,58 @@ class ChannelUsersRelation(models.Model): self.env['slide.slide.partner'].search(removed_slide_partner_domain).unlink() return super(ChannelUsersRelation, self).unlink() + def _set_as_completed(self): + """ Set record as completed and compute karma gains """ + partner_karma = dict.fromkeys(self.mapped('partner_id').ids, 0) + for record in self: + record.completed = True + partner_karma[record.partner_id.id] += record.channel_id.karma_gen_channel_finish + + partner_karma = { + partner_id: karma_to_add + for partner_id, karma_to_add in partner_karma.items() if karma_to_add > 0 + } + + if partner_karma: + users = self.env['res.users'].sudo().search([('partner_id', 'in', list(partner_karma.keys()))]) + for user in users: + users.add_karma(partner_karma[user.partner_id.id]) + + def _send_completed_mail(self): + """ Send an email to the attendee when he has successfully completed a course. """ + template_to_records = dict() + for record in self: + template = record.channel_id.completed_template_id + if template: + records = template_to_records.setdefault(template, self.env['slide.channel.partner']) + records += record + + for template, records in template_to_records.items(): + record_email_values = template.generate_email(self.ids, ['subject', 'body_html', 'email_from', 'partner_to']) + + mail_mail_values = [] + for record in self: + email_values = record_email_values.get(record.id) + if not email_values or not email_values.get('partner_ids'): + continue + + email_values.update( + author_id=self.channel_id.user_id.partner_id.id or self.env.company.partner_id.id, + auto_delete=True, + recipient_ids=[(4, pid) for pid in email_values['partner_ids']], + ) + email_values['body_html'] = template._render_encapsulate( + 'mail.mail_notification_light', email_values['body_html'], + add_context={ + 'message': self.env['mail.message'].sudo().new(dict(body=email_values['body_html'], record_name=record.channel_id.name)), + 'model_description': _('Completed Course') # tde fixme: translate into partner lang + } + ) + mail_mail_values.append(email_values) + + if mail_mail_values: + self.env['mail.mail'].sudo().create(mail_mail_values) + class Channel(models.Model): """ A channel is a container of slides. """ @@ -146,12 +196,15 @@ class Channel(models.Model): " * post comment and review on training course;") publish_template_id = fields.Many2one( 'mail.template', string='New Content Email', - help="Email template to send slide publication through email", + help="Email attendees once a new content is published", default=lambda self: self.env['ir.model.data'].xmlid_to_res_id('website_slides.slide_template_published')) share_template_id = fields.Many2one( 'mail.template', string='Share Template', help="Email template used when sharing a slide", default=lambda self: self.env['ir.model.data'].xmlid_to_res_id('website_slides.slide_template_shared')) + completed_template_id = fields.Many2one( + 'mail.template', string='Completion Email', help="Email attendees once they've finished the course", + default=lambda self: self.env['ir.model.data'].xmlid_to_res_id('website_slides.mail_template_channel_completed')) enroll = fields.Selection([ ('public', 'Public'), ('invite', 'On Invitation')], default='public', string='Enroll Policy', required=True, diff --git a/addons/website_slides/views/slide_channel_partner_views.xml b/addons/website_slides/views/slide_channel_partner_views.xml index 20227f7c5d3..8b88417d96b 100644 --- a/addons/website_slides/views/slide_channel_partner_views.xml +++ b/addons/website_slides/views/slide_channel_partner_views.xml @@ -13,13 +13,6 @@
- - Attendees - slide.channel.partner - tree - - - slide.channel.partner.tree slide.channel.partner @@ -39,6 +32,7 @@ Attendees slide.channel.partner tree,form + diff --git a/addons/website_slides/views/slide_channel_views.xml b/addons/website_slides/views/slide_channel_views.xml index 9637ffd2786..04a6363fc94 100644 --- a/addons/website_slides/views/slide_channel_views.xml +++ b/addons/website_slides/views/slide_channel_views.xml @@ -102,8 +102,9 @@ - + +