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 @@