From 786374399ef970e4499a73cef9b48a48be88704e Mon Sep 17 00:00:00 2001 From: Patrick Hoste Date: Wed, 4 Dec 2019 09:10:35 +0000 Subject: [PATCH] [IMP] website_slides: add possibility to request access to private course PURPOSE Before this commit, a user could not contact a course responsible to ask him to join. With this commit he is be able to do it. SPECIFICATIONS Change the message 'Contact website administrator' to 'Contact Responsible'. Add the name of the course responsible in the course description panel. When clicking on its name it opens a modal to request access to the course. It creates an activity linked to the customer and assigned to the responsible. When hovering the 'You're enrolled' button the fa-check icon changes to a fa-times icon to show that you can unsubscribe when clicking. LINKS Task ID : 2127364 PR : #40708 --- .../src/xml/website_slides_unsubscribe.xml | 2 +- addons/website_slides/__manifest__.py | 4 + .../data/mail_activity_data.xml | 11 +++ addons/website_slides/models/slide_channel.py | 65 ++++++++++++++- .../website_slides/static/src/js/activity.js | 47 +++++++++++ .../src/js/slides_course_enroll_email.js | 83 +++++++++++++++++++ .../static/src/js/slides_course_quiz.js | 23 ++++- .../static/src/scss/website_slides.scss | 31 +++++++ .../static/src/xml/activity.xml | 22 +++++ .../static/src/xml/slide_course_join.xml | 6 ++ .../static/src/xml/slide_quiz.xml | 19 ++++- addons/website_slides/views/assets.xml | 2 + .../views/slide_channel_views.xml | 1 + .../views/website_slides_templates_course.xml | 25 +++++- .../views/website_slides_templates_lesson.xml | 1 + 15 files changed, 335 insertions(+), 7 deletions(-) create mode 100644 addons/website_slides/data/mail_activity_data.xml create mode 100644 addons/website_slides/static/src/js/activity.js create mode 100644 addons/website_slides/static/src/js/slides_course_enroll_email.js create mode 100644 addons/website_slides/static/src/xml/activity.xml diff --git a/addons/website_sale_slides/static/src/xml/website_slides_unsubscribe.xml b/addons/website_sale_slides/static/src/xml/website_slides_unsubscribe.xml index d09d78dce08..1cadfbbf67d 100644 --- a/addons/website_sale_slides/static/src/xml/website_slides_unsubscribe.xml +++ b/addons/website_sale_slides/static/src/xml/website_slides_unsubscribe.xml @@ -2,7 +2,7 @@ - +

diff --git a/addons/website_slides/__manifest__.py b/addons/website_slides/__manifest__.py index 85a3b2d160c..fdb2c9e482c 100644 --- a/addons/website_slides/__manifest__.py +++ b/addons/website_slides/__manifest__.py @@ -48,6 +48,7 @@ Featuring 'wizard/slide_channel_invite_views.xml', 'data/gamification_data.xml', 'data/mail_data.xml', + 'data/mail_activity_data.xml', 'data/slide_data.xml', 'data/website_data.xml', ], @@ -58,6 +59,9 @@ Featuring 'data/slide_slide_demo.xml', 'data/slide_user_demo.xml', ], + 'qweb': [ + 'static/src/xml/activity.xml', + ], 'installable': True, 'application': True, } diff --git a/addons/website_slides/data/mail_activity_data.xml b/addons/website_slides/data/mail_activity_data.xml new file mode 100644 index 00000000000..df1563f489e --- /dev/null +++ b/addons/website_slides/data/mail_activity_data.xml @@ -0,0 +1,11 @@ + + + + + Access Request + fa-check-circle + 50 + + + + diff --git a/addons/website_slides/models/slide_channel.py b/addons/website_slides/models/slide_channel.py index 7a950e7d1b6..e410091b109 100644 --- a/addons/website_slides/models/slide_channel.py +++ b/addons/website_slides/models/slide_channel.py @@ -78,6 +78,7 @@ class Channel(models.Model): _description = 'Course' _inherit = [ 'mail.thread', 'rating.mixin', + 'mail.activity.mixin', 'image.mixin', 'website.seo.metadata', 'website.published.multi.mixin'] _order = 'sequence, id' @@ -159,6 +160,7 @@ class Channel(models.Model): string='Members', help="All members of the channel.", context={'active_test': False}, copy=False, depends=['channel_partner_ids']) members_count = fields.Integer('Attendees count', compute='_compute_members_count') members_done_count = fields.Integer('Attendees Done Count', compute='_compute_members_done_count') + has_requested_access = fields.Boolean(string='Access Requested', compute='_compute_has_requested_access', compute_sudo=False) is_member = fields.Boolean(string='Is Member', compute='_compute_is_member', compute_sudo=False) channel_partner_ids = fields.One2many('slide.channel.partner', 'channel_id', string='Members Information', groups='website_slides.group_website_slides_officer', depends=['partner_ids']) upload_group_ids = fields.Many2many( @@ -200,6 +202,17 @@ class Channel(models.Model): for channel in self: channel.members_done_count = data.get(channel.id, 0) + @api.depends('activity_ids.request_partner_id') + @api.depends_context('uid') + @api.model + def _compute_has_requested_access(self): + requested_cids = self.sudo().activity_search( + ['website_slides.mail_activity_data_access_request'], + additional_domain=[('request_partner_id', '=', self.env.user.partner_id.id)] + ).mapped('res_id') + for channel in self: + channel.has_requested_access = channel.id in requested_cids + @api.depends('channel_partner_ids.partner_id') @api.depends_context('uid') @api.model @@ -394,6 +407,7 @@ class Channel(models.Model): if vals.get('user_id'): self._action_add_members(self.env['res.users'].sudo().browse(vals['user_id']).partner_id) + self.activity_reschedule(['website_slides.mail_activity_data_access_request'], new_user_id=vals.get('user_id')) if 'enroll_group_ids' in vals: self._add_groups_members() @@ -590,8 +604,41 @@ class Channel(models.Model): action['domain'] = [('res_id', 'in', self.ids)] return action + def action_request_access(self): + """ Request access to the channel. Returns a dict with keys being either 'error' + (specific error raised) or 'done' (request done or not). """ + if self.env.user.has_group('base.group_public'): + return {'error': _('You have to sign in before')} + if not self.is_published: + return {'error': _('Course not published yet')} + if self.is_member: + return {'error': _('Already member')} + if self.enroll == 'invite': + activities = self.sudo()._action_request_access(self.env.user.partner_id) + if activities: + return {'done': True} + return {'error': _('Already Requested')} + return {'done': False} + + def action_grant_access(self, partner_id): + partner = self.env['res.partner'].browse(partner_id).exists() + if partner: + if self._action_add_members(partner): + self.activity_search( + ['website_slides.mail_activity_data_access_request'], + user_id=self.user_id.id, additional_domain=[('request_partner_id', '=', partner.id)] + ).action_feedback(feedback=_('Access Granted')) + + def action_refuse_access(self, partner_id): + partner = self.env['res.partner'].browse(partner_id).exists() + if partner: + self.activity_search( + ['website_slides.mail_activity_data_access_request'], + user_id=self.user_id.id, additional_domain=[('request_partner_id', '=', partner.id)] + ).action_feedback(feedback=_('Access Refused')) + # --------------------------------------------------------- - # Rating Mixin API + # Mailing Mixin API # --------------------------------------------------------- def _rating_domain(self): @@ -599,6 +646,22 @@ class Channel(models.Model): domain = super(Channel, self)._rating_domain() return expression.AND([domain, [('is_internal', '=', False)]]) + def _action_request_access(self, partner): + activities = self.env['mail.activity'] + requested_cids = self.sudo().activity_search( + ['website_slides.mail_activity_data_access_request'], + additional_domain=[('request_partner_id', '=', partner.id)] + ).mapped('res_id') + for channel in self: + if channel.id not in requested_cids: + activities += channel.activity_schedule( + 'website_slides.mail_activity_data_access_request', + note=_('%s is requesting access to this course.') % partner.name, + user_id=channel.user_id.id, + request_partner_id=partner.id + ) + return activities + # --------------------------------------------------------- # Data / Misc # --------------------------------------------------------- diff --git a/addons/website_slides/static/src/js/activity.js b/addons/website_slides/static/src/js/activity.js new file mode 100644 index 00000000000..bc207acbd06 --- /dev/null +++ b/addons/website_slides/static/src/js/activity.js @@ -0,0 +1,47 @@ +odoo.define('website_slides.Activity', function (require) { +"use strict"; + +var field_registry = require('web.field_registry'); + +require('mail.Activity'); + +var KanbanActivity = field_registry.get('kanban_activity'); +var MailActivity = field_registry.get('mail_activity'); + +function applyInclude(Activity) { + Activity.include({ + events: _.extend({}, Activity.prototype.events, { + 'click .o_activity_action_grant_access': '_onGrantAccess', + 'click .o_activity_action_refuse_access': '_onRefuseAccess', + }), + + _onGrantAccess: function (event) { + var self = this; + var partnerId = $(event.currentTarget).data('partner-id'); + this._rpc({ + model: 'slide.channel', + method: 'action_grant_access', + args: [this.res_id, partnerId], + }).then(function (result) { + self.trigger_up('reload'); + }); + }, + + _onRefuseAccess: function (event) { + var self = this; + var partnerId = $(event.currentTarget).data('partner-id'); + this._rpc({ + model: 'slide.channel', + method: 'action_refuse_access', + args: [this.res_id, partnerId], + }).then(function () { + self.trigger_up('reload'); + }); + }, + }); +} + +applyInclude(KanbanActivity); +applyInclude(MailActivity); + +}); \ No newline at end of file diff --git a/addons/website_slides/static/src/js/slides_course_enroll_email.js b/addons/website_slides/static/src/js/slides_course_enroll_email.js new file mode 100644 index 00000000000..a9f5f799a79 --- /dev/null +++ b/addons/website_slides/static/src/js/slides_course_enroll_email.js @@ -0,0 +1,83 @@ +odoo.define('website_slides.course.enroll', function (require) { +'use strict'; + +var core = require('web.core'); +var Dialog = require('web.Dialog'); +var publicWidget = require('web.public.widget'); +var _t = core._t; + +var SlideEnrollDialog = Dialog.extend({ + template: 'slide.course.join.request', + + init: function (parent, options, modalOptions) { + modalOptions = _.defaults(modalOptions || {}, { + title: _t('Request Access.'), + size: 'medium', + buttons: [{ + text: _t('Yes'), + classes: 'btn-primary', + click: this._onSendRequest.bind(this) + }, { + text: _t('Cancel'), + close: true + }] + }); + this.$element = options.$element; + this.channelId = options.channelId; + this._super(parent, modalOptions); + }, + + _onSendRequest: function () { + var self = this; + this._rpc({ + model: 'slide.channel', + method: 'action_request_access', + args: [self.channelId] + }).then(function (result) { + if (result.error) { + self.$element.replaceWith('

'); + } else if (result.done) { + self.$element.replaceWith(''); + } else { + self.$element.replaceWith(''); + } + self.close(); + }); + } + +}); + +publicWidget.registry.websiteSlidesEnroll = publicWidget.Widget.extend({ + selector: '.o_wslides_js_channel_enroll', + xmlDependencies: ['/website_slides/static/src/xml/slide_course_join.xml'], + events: { + 'click': '_onSendRequestClick', + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + _openDialog: function (channelId) { + new SlideEnrollDialog(this, { + channelId: channelId, + $element: this.$el + }).open(); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + _onSendRequestClick: function (ev) { + ev.preventDefault(); + this._openDialog($(ev.currentTarget).data('channelId')); + } +}); + +return { + slideEnrollDialog: SlideEnrollDialog, + websiteSlidesEnroll: publicWidget.registry.websiteSlidesEnroll +}; + +}); diff --git a/addons/website_slides/static/src/js/slides_course_quiz.js b/addons/website_slides/static/src/js/slides_course_quiz.js index 50ac2a3cef1..d07e68f2344 100644 --- a/addons/website_slides/static/src/js/slides_course_quiz.js +++ b/addons/website_slides/static/src/js/slides_course_quiz.js @@ -10,6 +10,8 @@ odoo.define('website_slides.quiz', function (require) { var QuestionFormWidget = require('website_slides.quiz.question.form'); var SlideQuizFinishModal = require('website_slides.quiz.finish'); + var SlideEnrollDialog = require('website_slides.course.enroll').slideEnrollDialog; + var QWeb = core.qweb; var _t = core._t; @@ -26,7 +28,10 @@ odoo.define('website_slides.quiz', function (require) { */ var Quiz = publicWidget.Widget.extend({ template: 'slide.slide.quiz', - xmlDependencies: ['/website_slides/static/src/xml/slide_quiz.xml'], + xmlDependencies: [ + '/website_slides/static/src/xml/slide_quiz.xml', + '/website_slides/static/src/xml/slide_course_join.xml' + ], events: { "click .o_wslides_quiz_answer": '_onAnswerClick', "click .o_wslides_js_lesson_quiz_submit": '_submitQuiz', @@ -36,6 +41,7 @@ odoo.define('website_slides.quiz', function (require) { 'click .o_wslides_js_quiz_add': '_onCreateQuizClick', 'click .o_wslides_js_quiz_edit_question': '_onEditQuestionClick', 'click .o_wslides_js_quiz_delete_question': '_onDeleteQuestionClick', + 'click .o_wslides_js_channel_enroll': '_onSendRequestToResponsibleClick', }, custom_events: { @@ -531,6 +537,20 @@ odoo.define('website_slides.quiz', function (require) { }).open(); }, + /** + * Handler for the contact responsible link below a Quiz + * @param ev + * @private + */ + _onSendRequestToResponsibleClick: function(ev) { + ev.preventDefault(); + var channelId = $(ev.currentTarget).data('channelId'); + new SlideEnrollDialog(this, { + channelId: channelId, + $element: $(ev.currentTarget).closest('.alert.alert-info') + }).open(); + }, + /** * Displays the created Question at the correct place (after the last question or * at the first place if there is no questions yet) It also displays the 'Add Question' @@ -718,6 +738,7 @@ odoo.define('website_slides.quiz', function (require) { return { channelId: slideData.channelId, channelEnroll: slideData.channelEnroll, + channelRequestedAccess: slideData.channelRequestedAccess || false, signupAllowed: slideData.signupAllowed }; }, diff --git a/addons/website_slides/static/src/scss/website_slides.scss b/addons/website_slides/static/src/scss/website_slides.scss index 815ae9aa050..1f4175d1e99 100644 --- a/addons/website_slides/static/src/scss/website_slides.scss +++ b/addons/website_slides/static/src/scss/website_slides.scss @@ -237,6 +237,37 @@ $o-wslides-fs-side-width: 300px; @include media-breakpoint-up(md) { border-top-width: 0; } + + .o_wslides_js_channel_unsubscribe { + > .fa-times { + display: none; + } + + &:hover { + > .fa-check { + display: none; + } + + > .fa-times { + display: inline-block; + } + } + } + + .o_wslides_js_channel_enroll { + cursor: pointer; + + &:hover, &:hover .o_wslides_enroll_msg small { + font-weight: bold; + } + } + + .o_wslides_enroll_msg { + p { + display: inline-block; + margin-bottom: 0px; + } + } } @mixin o-wslides-tabs($tab-active-color: $o-wslides-color-bg) { diff --git a/addons/website_slides/static/src/xml/activity.xml b/addons/website_slides/static/src/xml/activity.xml new file mode 100644 index 00000000000..20c683ab3c4 --- /dev/null +++ b/addons/website_slides/static/src/xml/activity.xml @@ -0,0 +1,22 @@ + + + + + + + + Grant Access + + + Refuse Access + + + + +
+ +
+
+
+
+
\ No newline at end of file diff --git a/addons/website_slides/static/src/xml/slide_course_join.xml b/addons/website_slides/static/src/xml/slide_course_join.xml index 5c890cd027d..0241915c190 100644 --- a/addons/website_slides/static/src/xml/slide_course_join.xml +++ b/addons/website_slides/static/src/xml/slide_course_join.xml @@ -15,4 +15,10 @@
+ + +
+

Do you want to request access to this course ?

+
+
diff --git a/addons/website_slides/static/src/xml/slide_quiz.xml b/addons/website_slides/static/src/xml/slide_quiz.xml index e64bc96604e..aa7bcc3f835 100644 --- a/addons/website_slides/static/src/xml/slide_quiz.xml +++ b/addons/website_slides/static/src/xml/slide_quiz.xml @@ -47,8 +47,23 @@
- - This course is private. Contact the website administrator to enroll. + This course is private. + + Please + + sign in + + to enroll. + + + + Responsible already contacted. + + + Contact the responsible to enroll. + + diff --git a/addons/website_slides/views/assets.xml b/addons/website_slides/views/assets.xml index 3d896e8473e..78a54ef2f68 100644 --- a/addons/website_slides/views/assets.xml +++ b/addons/website_slides/views/assets.xml @@ -7,6 +7,7 @@