[MERGE] website slides: add course tags to eLearning frontend
PURPOSE Add more visibility to course tags as well as the ability to add new ones via the front-end. SPECIFICATIONS Course tags are only viewable in the courses page. In this merge we add more course tag functionality by: - making course tag creation more intuitive via backend - making course tags viewable within a course's specific webpage - making it possible for those with the correct access rights to add new course tags via the course's specific webpage (via a "+ Add Tag" button) - adding the ability to assign colors to course tags - note that coloring should not show when courses page is filtered by course tag to allow for easier identification of tag filters. See sub commits for more details. LINKS Task: 2128166 Migration PR: odoo/upgrade#862 closes odoo/odoo#44365 Related: odoo/upgrade#862 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -549,6 +549,69 @@ class WebsiteSlides(WebsiteProfile):
|
||||
'can_create': can_create,
|
||||
}
|
||||
|
||||
@http.route(['/slides/channel/tag/group/search_read'], type='json', auth='user', methods=['POST'], website=True)
|
||||
def slide_channel_tag_group_search_read(self, fields, domain):
|
||||
can_create = request.env['slide.channel.tag.group'].check_access_rights('create', raise_exception=False)
|
||||
return {
|
||||
'read_results': request.env['slide.channel.tag.group'].search_read(domain, fields),
|
||||
'can_create': can_create,
|
||||
}
|
||||
|
||||
@http.route('/slides/channel/tag/add', type='json', auth='user', methods=['POST'], website=True)
|
||||
def slide_channel_tag_add(self, channel_id, tag_id=None, group_id=None):
|
||||
""" Adds a slide channel tag to the specified slide channel.
|
||||
|
||||
:param integer channel_id: Channel ID
|
||||
:param list tag_id: Channel Tag ID as first value of list. If id=0, then this is a new tag to
|
||||
generate and expects a second list value of the name of the new tag.
|
||||
:param list group_id: Channel Tag Group ID as first value of list. If id=0, then this is a new
|
||||
tag group to generate and expects a second list value of the name of the
|
||||
new tag group. This value is required for when a new tag is being created.
|
||||
|
||||
tag_id and group_id values are provided by a Select2. Default "None" values allow for
|
||||
graceful failures in exceptional cases when values are not provided.
|
||||
|
||||
:return: channel's course page
|
||||
"""
|
||||
|
||||
# handle exception during addition of course tag and send error notification to the client
|
||||
# otherwise client slide create dialog box continue processing even server fail to create a slide
|
||||
try:
|
||||
channel = request.env['slide.channel'].browse(int(channel_id))
|
||||
can_upload = channel.can_upload
|
||||
can_publish = channel.can_publish
|
||||
except (UserError, AccessError) as e:
|
||||
_logger.error(e)
|
||||
return {'error': e.name}
|
||||
else:
|
||||
if not can_upload or not can_publish:
|
||||
return {'error': _('You cannot add tags to this course.')}
|
||||
|
||||
if tag_id:
|
||||
# handle creation of new channel tag
|
||||
if tag_id[0] == 0:
|
||||
if group_id:
|
||||
# handle creation of new channel tag group
|
||||
if group_id[0] == 0:
|
||||
tag_group = request.env['slide.channel.tag.group'].create({
|
||||
'name': group_id[1]['name'],
|
||||
})
|
||||
group_id = tag_group.id
|
||||
# use existing channel tag group
|
||||
else:
|
||||
group_id = group_id[0]
|
||||
else:
|
||||
return {'error': _('Missing "Tag Group" for creating a new "Tag".')}
|
||||
|
||||
request.env['slide.channel.tag'].create({'name': tag_id[1]['name'],
|
||||
'channel_ids': [channel.id],
|
||||
'group_id': group_id,
|
||||
})
|
||||
else:
|
||||
# use existing channel tag
|
||||
request.env['slide.channel.tag'].browse(tag_id[0]).write({'channel_ids': [(4, channel.id, 0)]})
|
||||
return {'url': "/slides/%s" % (slug(channel))}
|
||||
|
||||
@http.route(['/slides/channel/subscribe'], type='json', auth='user', website=True)
|
||||
def slide_channel_subscribe(self, channel_id):
|
||||
return request.env['slide.channel'].browse(channel_id).message_subscribe(partner_ids=[request.env.user.partner_id.id])
|
||||
|
||||
@@ -6,14 +6,17 @@
|
||||
</record>
|
||||
<record id="slide_channel_tag_level_basic" model="slide.channel.tag">
|
||||
<field name="name">Basic</field>
|
||||
<field name="color">10</field>
|
||||
<field name="group_id" ref="website_slides.slide_channel_tag_group_level"/>
|
||||
</record>
|
||||
<record id="slide_channel_tag_level_intermediate" model="slide.channel.tag">
|
||||
<field name="name">Intermediate</field>
|
||||
<field name="color">3</field>
|
||||
<field name="group_id" ref="website_slides.slide_channel_tag_group_level"/>
|
||||
</record>
|
||||
<record id="slide_channel_tag_level_advanced" model="slide.channel.tag">
|
||||
<field name="name">Advanced</field>
|
||||
<field name="color">1</field>
|
||||
<field name="group_id" ref="website_slides.slide_channel_tag_group_level"/>
|
||||
</record>
|
||||
|
||||
@@ -23,14 +26,17 @@
|
||||
</record>
|
||||
<record id="slide_channel_tag_role_gardener" model="slide.channel.tag">
|
||||
<field name="name">Gardener</field>
|
||||
<field name="color">10</field>
|
||||
<field name="group_id" ref="website_slides.slide_channel_tag_group_role"/>
|
||||
</record>
|
||||
<record id="slide_channel_tag_role_carpenter" model="slide.channel.tag">
|
||||
<field name="name">Carpenter</field>
|
||||
<field name="color">11</field>
|
||||
<field name="group_id" ref="website_slides.slide_channel_tag_group_role"/>
|
||||
</record>
|
||||
<record id="slide_channel_tag_role_furniture" model="slide.channel.tag">
|
||||
<field name="name">Furniture Designer</field>
|
||||
<field name="color">3</field>
|
||||
<field name="group_id" ref="website_slides.slide_channel_tag_group_role"/>
|
||||
</record>
|
||||
|
||||
@@ -40,10 +46,11 @@
|
||||
</record>
|
||||
<record id="slide_channel_tag_other_1" model="slide.channel.tag">
|
||||
<field name="name">Certification</field>
|
||||
<field name="color">7</field>
|
||||
<field name="group_id" ref="website_slides.slide_channel_tag_group_data_other"/>
|
||||
</record>
|
||||
<record id="slide_channel_tag_other_2" model="slide.channel.tag">
|
||||
<field name="name">Dog Friendly</field>
|
||||
<field name="group_id" ref="website_slides.slide_channel_tag_group_data_other"/>
|
||||
</record>
|
||||
</data></odoo>
|
||||
</data></odoo>
|
||||
|
||||
@@ -6,11 +6,11 @@ from odoo import fields, models
|
||||
|
||||
class SlideChannelTagGroup(models.Model):
|
||||
_name = 'slide.channel.tag.group'
|
||||
_description = 'Channel/Course Tag'
|
||||
_description = 'Channel/Course Groups'
|
||||
_inherit = 'website.published.mixin'
|
||||
_order = 'sequence asc'
|
||||
|
||||
name = fields.Char('Name', required=True, translate=True)
|
||||
name = fields.Char('Group Name', required=True, translate=True)
|
||||
sequence = fields.Integer('Sequence', default=10, index=True, required=True)
|
||||
tag_ids = fields.One2many('slide.channel.tag', 'group_id', string='Tags')
|
||||
|
||||
@@ -30,3 +30,4 @@ class SlideChannelTag(models.Model):
|
||||
'Group sequence', related='group_id.sequence',
|
||||
index=True, readonly=True, store=True)
|
||||
channel_ids = fields.Many2many('slide.channel', 'slide_channel_tag_rel', 'tag_id', 'channel_id', string='Channels')
|
||||
color = fields.Integer(string='Color Index', help="Color to apply to this tag (including in website).")
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
odoo.define('website_slides.channel_tag.add', 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 TagCourseDialog = Dialog.extend({
|
||||
template: 'website.slides.tag.add',
|
||||
events: _.extend({}, Dialog.prototype.events, {
|
||||
'change input#tag_id' : '_onChangeTag',
|
||||
}),
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @param {Object} parent
|
||||
* @param {Object} options holding channelId
|
||||
*
|
||||
*/
|
||||
init: function (parent, options) {
|
||||
options = _.defaults(options || {}, {
|
||||
title: _t("Add a tag"),
|
||||
size: 'medium',
|
||||
buttons: [{
|
||||
text: _t("Add"),
|
||||
classes: 'btn-primary',
|
||||
click: this._onClickFormSubmit.bind(this)
|
||||
}, {
|
||||
text: _t("Discard"),
|
||||
close: true
|
||||
}]
|
||||
});
|
||||
|
||||
this.channelID = parseInt(options.channelId, 10);
|
||||
this.tagIds = options.channelTagIds || [];
|
||||
this._super(parent, options);
|
||||
},
|
||||
start: function () {
|
||||
var self = this;
|
||||
return this._super.apply(this, arguments).then(function () {
|
||||
self._bindSelect2Dropdown();
|
||||
self._hideTagGroup();
|
||||
});
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 'Tag' and 'Tag Group' management for select2
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_bindSelect2Dropdown: function () {
|
||||
var self = this;
|
||||
this.$('#tag_id').select2(this._select2Wrapper(_t('Tag'),
|
||||
function () {
|
||||
return self._rpc({
|
||||
route: '/slides/channel/tag/search_read',
|
||||
params: {
|
||||
fields: ['name'],
|
||||
domain: [['id','not in',self.tagIds]],
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
this.$('#tag_group_id').select2(this._select2Wrapper(_t('Tag Group (required for new tags)'),
|
||||
function () {
|
||||
return self._rpc({
|
||||
route: '/slides/channel/tag/group/search_read',
|
||||
params: {
|
||||
fields: ['name'],
|
||||
domain: [],
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Wrapper for select2 load data from server at once and store it.
|
||||
*
|
||||
* @private
|
||||
* @param {String} Placeholder for element.
|
||||
* @param {Function} Function to fetch data from remote location should return a Promise
|
||||
* resolved data should be array of object with id and name. eg. [{'id': id, 'name': 'text'}, ...]
|
||||
* @param {String} [nameKey='name'] (optional) the name key of the returned record
|
||||
* ('name' if not provided)
|
||||
* @returns {Object} select2 wrapper object
|
||||
*/
|
||||
_select2Wrapper: function (tag, fetchFNC, nameKey) {
|
||||
nameKey = nameKey || 'name';
|
||||
|
||||
var values = {
|
||||
width: '100%',
|
||||
placeholder: tag,
|
||||
allowClear: true,
|
||||
formatNoMatches: false,
|
||||
selection_data: false,
|
||||
fetch_rpc_fnc: fetchFNC,
|
||||
formatSelection: function (data) {
|
||||
if (data.tag) {
|
||||
data.text = data.tag;
|
||||
}
|
||||
return data.text;
|
||||
},
|
||||
createSearchChoice: function (term, data) {
|
||||
var addedTags = $(this.opts.element).select2('data');
|
||||
if (_.filter(_.union(addedTags, data), function (tag) {
|
||||
return tag.text.toLowerCase().localeCompare(term.toLowerCase()) === 0;
|
||||
}).length === 0) {
|
||||
if (this.opts.can_create) {
|
||||
return {
|
||||
id: _.uniqueId('tag_'),
|
||||
create: true,
|
||||
tag: term,
|
||||
text: _.str.sprintf(_t("Create new %s '%s'"), tag, term),
|
||||
};
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
fill_data: function (query, data) {
|
||||
var that = this,
|
||||
tags = {results: []};
|
||||
_.each(data, function (obj) {
|
||||
if (that.matcher(query.term, obj[nameKey])) {
|
||||
tags.results.push({id: obj.id, text: obj[nameKey]});
|
||||
}
|
||||
});
|
||||
query.callback(tags);
|
||||
},
|
||||
query: function (query) {
|
||||
var that = this;
|
||||
// fetch data only once and store it
|
||||
if (!this.selection_data) {
|
||||
this.fetch_rpc_fnc().then(function (data) {
|
||||
that.can_create = data.can_create;
|
||||
that.fill_data(query, data.read_results);
|
||||
that.selection_data = data.read_results;
|
||||
});
|
||||
} else {
|
||||
this.fill_data(query, this.selection_data);
|
||||
}
|
||||
}
|
||||
};
|
||||
return values;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get value for tag_id and [when appropriate] tag_group_id to send to server
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_getSelect2DropdownValues: function () {
|
||||
var result = {};
|
||||
var tag = this.$('#tag_id').select2('data');
|
||||
if (tag) {
|
||||
if (tag.create) {
|
||||
// new tag
|
||||
var group = this.$('#tag_group_id').select2('data');
|
||||
if(group) {
|
||||
result['tag_id'] = [0, {'name': tag.text}]
|
||||
if (group.create) {
|
||||
// new tag group
|
||||
result['group_id'] = [0, {'name': group.text}];
|
||||
} else {
|
||||
result['group_id'] = [group.id];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result['tag_id'] = [tag.id];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Select2 fields makes the "required" input hidden on the interface.
|
||||
* Therefore we need to make a method to visually provide this requirement
|
||||
* feedback to users. "tag group" field should only need this when a new tag
|
||||
* is created.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_formValidate: function ($form) {
|
||||
$form.addClass('was-validated');
|
||||
var result = $form[0].checkValidity();
|
||||
|
||||
var $tagInput = this.$('#tag_id');
|
||||
if ($tagInput.length !== 0){
|
||||
var $tagSelect2Container = $tagInput
|
||||
.closest('.form-group')
|
||||
.find('.select2-container');
|
||||
$tagSelect2Container.removeClass('is-invalid is-valid');
|
||||
if ($tagInput.is(':invalid')) {
|
||||
$tagSelect2Container.addClass('is-invalid');
|
||||
} else if ($tagInput.is(':valid')) {
|
||||
$tagSelect2Container.addClass('is-valid');
|
||||
var $tagGroupInput = this.$('#tag_group_id');
|
||||
if ($tagGroupInput.length !== 0){
|
||||
var $tagGroupSelect2Container = $tagGroupInput
|
||||
.closest('.form-group')
|
||||
.find('.select2-container');
|
||||
if ($tagGroupInput.is(':invalid')) {
|
||||
$tagGroupSelect2Container.addClass('is-invalid');
|
||||
} else if ($tagGroupInput.is(':valid')) {
|
||||
$tagGroupSelect2Container.addClass('is-valid');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
_alertDisplay: function (message) {
|
||||
this._alertRemove();
|
||||
$('<div/>', {
|
||||
"class": 'alert alert-warning',
|
||||
role: 'alert'
|
||||
}).text(message).insertBefore(this.$('form'));
|
||||
},
|
||||
_alertRemove: function () {
|
||||
this.$('.alert-warning').remove();
|
||||
},
|
||||
|
||||
/**
|
||||
* When the user IS NOT creating a new tag, this function hides the group tag field
|
||||
* and makes it not required. Since the select2 field makes an extra container, this
|
||||
* needs to be hidden along with the group tag input field and its label.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_hideTagGroup: function () {
|
||||
var $tag_group_id = this.$('#tag_group_id');
|
||||
var $tagGroupSelect2Container = $tag_group_id.closest('.form-group');
|
||||
$tagGroupSelect2Container.hide();
|
||||
$tag_group_id.removeAttr("required");
|
||||
$tag_group_id.select2("val", "");
|
||||
},
|
||||
|
||||
/**
|
||||
* When the user IS creating a new tag, this function shows the field and
|
||||
* makes it required. Since the select2 field makes an extra container, this
|
||||
* needs to be shown along with the group input field and its label.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_showTagGroup: function () {
|
||||
var $tag_group_id = this.$('#tag_group_id');
|
||||
var $tagGroupSelect2Container = $tag_group_id.closest('.form-group');
|
||||
$tagGroupSelect2Container.show();
|
||||
$tag_group_id.attr("required", "required");
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handler
|
||||
//--------------------------------------------------------------------------
|
||||
_onClickFormSubmit: function () {
|
||||
var self = this;
|
||||
var $form = this.$('#slides_channel_tag_add_form');
|
||||
if (this._formValidate($form)) {
|
||||
var values = this._getSelect2DropdownValues();
|
||||
return this._rpc({
|
||||
route: '/slides/channel/tag/add',
|
||||
params: {'channel_id': this.channelID,
|
||||
'tag_id': values.tag_id,
|
||||
'group_id': values.group_id},
|
||||
}).then(function (data) {
|
||||
if (data.error) {
|
||||
self._alertDisplay(data.error);
|
||||
} else {
|
||||
window.location = data.url;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_onChangeTag: function (ev) {
|
||||
var self = this;
|
||||
var tag = $(ev.currentTarget).select2('data');
|
||||
if (tag && tag.create) {
|
||||
self._showTagGroup();
|
||||
} else {
|
||||
self._hideTagGroup();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
publicWidget.registry.websiteSlidesTag = publicWidget.Widget.extend({
|
||||
selector: '.o_wslides_js_channel_tag_add',
|
||||
xmlDependencies: ['/website_slides/static/src/xml/website_slides_channel_tag.xml'],
|
||||
events: {
|
||||
'click': '_onAddTagClick',
|
||||
},
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
_openDialog: function ($element) {
|
||||
var data = $element.data();
|
||||
return new TagCourseDialog(this, data).open();
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onAddTagClick: function (ev) {
|
||||
ev.preventDefault();
|
||||
this._openDialog($(ev.currentTarget));
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
TagCourseDialog: TagCourseDialog,
|
||||
websiteSlidesTag: publicWidget.registry.websiteSlidesTag
|
||||
};
|
||||
|
||||
});
|
||||
@@ -58,6 +58,36 @@ $o-wslides-fs-side-width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
// Color tags according to assigned background color.
|
||||
.o_wslides_channel_tag {
|
||||
vertical-align: middle;
|
||||
@for $size from 1 through length($o-colors) {
|
||||
&.o_tag_color_#{$size - 1} {
|
||||
$background-color: white;
|
||||
// no color selected
|
||||
@if $size == 1 {
|
||||
& {
|
||||
color: black;
|
||||
background-color: $background-color;
|
||||
box-shadow: inset 0 0 0 1px nth($o-colors, $size);
|
||||
}
|
||||
} @else {
|
||||
$background-color: nth($o-colors, $size);
|
||||
& {
|
||||
color: white;
|
||||
background-color: $background-color;
|
||||
}
|
||||
}
|
||||
@at-root a#{&} {
|
||||
&:hover {
|
||||
color: color-yiq($background-color);
|
||||
background-color: darken($background-color, 10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_wslides_body {
|
||||
background-color: $o-wslides-color-bg;
|
||||
|
||||
@@ -163,7 +193,7 @@ $o-wslides-fs-side-width: 300px;
|
||||
@each $limit in $truncate-limits {
|
||||
.o_wslides_desc_truncate_#{$limit} {
|
||||
$line-height: 1.3;
|
||||
max-height: $limit * $line-height * 1em;
|
||||
max-height: $limit * $line-height * 1.2em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: normal;
|
||||
|
||||
@@ -75,6 +75,8 @@ tour.register('course_publisher', {
|
||||
run: function () {} // check membership
|
||||
}
|
||||
].concat(
|
||||
slidesTourTools.addExistingCourseTag(),
|
||||
slidesTourTools.addNewCourseTag('The Most Awesome Course'),
|
||||
slidesTourTools.addSection('Introduction'),
|
||||
slidesTourTools.addVideoToSection('Introduction'),
|
||||
[{
|
||||
|
||||
@@ -73,7 +73,7 @@ var addWebPageToSection = function (sectionName, pageName) {
|
||||
content: 'eLearning: click on tags',
|
||||
trigger: 'ul.select2-choices:first',
|
||||
}, {
|
||||
content: 'eLearning: select gardener tag',
|
||||
content: 'eLearning: select Theory tag',
|
||||
trigger: 'div.select2-result-label:contains("Theory")',
|
||||
in_modal: false,
|
||||
}, {
|
||||
@@ -83,10 +83,59 @@ var addWebPageToSection = function (sectionName, pageName) {
|
||||
}];
|
||||
};
|
||||
|
||||
var addExistingCourseTag = function () {
|
||||
return [
|
||||
{
|
||||
content: 'eLearning: click on Add Tag',
|
||||
trigger: 'a.o_wslides_js_channel_tag_add',
|
||||
}, {
|
||||
content: 'eLearning: click on tag dropdown',
|
||||
trigger: 'a.select2-choice:first',
|
||||
}, {
|
||||
content: 'eLearning: select advanced tag',
|
||||
trigger: 'div.select2-result-label:contains("Advanced")',
|
||||
in_modal: false,
|
||||
}, {
|
||||
content: 'eLearning: add existing course tag',
|
||||
trigger: 'footer.modal-footer button:contains("Add")'
|
||||
}];
|
||||
};
|
||||
|
||||
var addNewCourseTag = function (courseTagName) {
|
||||
return [
|
||||
{
|
||||
content: 'eLearning: click on Add Tag',
|
||||
trigger: 'a.o_wslides_js_channel_tag_add',
|
||||
}, {
|
||||
content: 'eLearning: click on tag dropdown',
|
||||
trigger: 'a.select2-choice:first',
|
||||
}, {
|
||||
content: 'eLearning: add a new course tag',
|
||||
trigger: 'a.select2-choice:first',
|
||||
run: function () {
|
||||
// directly add new tag since we can assume select2 works correctly
|
||||
$('#tag_id').select2('data', {id:'123', text: courseTagName, create: true});
|
||||
$('#tag_id').trigger('change');
|
||||
}
|
||||
}, {
|
||||
content: 'eLearning: click on tag group dropdown',
|
||||
trigger: 'a.select2-choice:last',
|
||||
}, {
|
||||
content: 'eLearning: select Tags tag group',
|
||||
trigger: 'div.select2-result-label:contains("Tags")',
|
||||
in_modal: false,
|
||||
}, {
|
||||
content: 'eLearning: add new course tag',
|
||||
trigger: 'footer.modal-footer button:contains("Add")'
|
||||
}];
|
||||
};
|
||||
|
||||
return {
|
||||
addSection: addSection,
|
||||
addVideoToSection: addVideoToSection,
|
||||
addWebPageToSection: addWebPageToSection,
|
||||
addExistingCourseTag: addExistingCourseTag,
|
||||
addNewCourseTag: addNewCourseTag,
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="website.slides.tag.add">
|
||||
<div class="form-group">
|
||||
<form action="/slides/channel/tag/add" method="POST" id="slides_channel_tag_add_form">
|
||||
<div class="form-group">
|
||||
<label for="tag_id" class="col-form-label">Tag</label>
|
||||
<input class="form-control" id="tag_id" required="required"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label id="tag_group_label" for="tag_group_id" class="col-form-label">Tag Group</label>
|
||||
<input class="form-control" id="tag_group_id"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -30,6 +30,7 @@
|
||||
<script type="text/javascript" src="/website_slides/static/src/js/slides_course_quiz.js"/>
|
||||
<script type="text/javascript" src="/website_slides/static/src/js/slides_course_quiz_question_form.js"/>
|
||||
<script type="text/javascript" src="/website_slides/static/src/js/slides_course_quiz_finish.js"/>
|
||||
<script type="text/javascript" src="/website_slides/static/src/js/slides_course_tag_add.js"/>
|
||||
<script type="text/javascript" src="/website_slides/static/src/js/slides_course_unsubscribe.js"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="slide_channel_tag_action" model="ir.actions.act_window">
|
||||
<field name="name">Course Tags</field>
|
||||
<field name="res_model">slide.channel.tag</field>
|
||||
@@ -64,9 +64,23 @@
|
||||
<form string="Course Tag Group">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="is_published" string="Menu entry"/>
|
||||
<field name="tag_ids"/>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only" string="Group Name"/>
|
||||
<h1><field name="name" default_focus="1" placeholder="Group Name"/></h1>
|
||||
<label for="is_published" string="Menu Entry"/>
|
||||
<field name="is_published"/><br/>
|
||||
<field name="tag_ids">
|
||||
<tree editable="bottom">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="group_sequence" invisible="1"/>
|
||||
<field name="name" string="Tag Name"/>
|
||||
<field name="color" string="Color" widget="color_picker"/>
|
||||
<control>
|
||||
<create string="Add a tag"/>
|
||||
</control>
|
||||
</tree>
|
||||
</field>
|
||||
</div>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
@@ -86,7 +100,7 @@
|
||||
</record>
|
||||
|
||||
<record id="slide_channel_tag_group_action" model="ir.actions.act_window">
|
||||
<field name="name">Course Tags</field>
|
||||
<field name="name">Course Groups</field>
|
||||
<field name="res_model">slide.channel.tag.group</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="tag_ids" widget="many2many_tags" placeholder="Tags"/>
|
||||
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}" placeholder="Tags"/>
|
||||
</div>
|
||||
<notebook colspan="4">
|
||||
<page name="content" string="Content">
|
||||
|
||||
@@ -67,8 +67,8 @@
|
||||
sequence="1"
|
||||
action="website_slides_action_settings"
|
||||
groups="base.group_system"/>
|
||||
<menuitem name="Course Tags"
|
||||
id="website_slides_menu_config_course_tags"
|
||||
<menuitem name="Course Groups"
|
||||
id="website_slides_menu_config_course_groups"
|
||||
parent="website_slides_menu_configuration"
|
||||
sequence="2"
|
||||
action="slide_channel_tag_group_action"/>
|
||||
@@ -78,4 +78,4 @@
|
||||
sequence="3"
|
||||
action="action_slide_tag"/>
|
||||
|
||||
</odoo>
|
||||
</odoo>
|
||||
|
||||
@@ -193,6 +193,22 @@
|
||||
|
||||
<div class="tab-content py-4 o_wslides_tabs_content mb-4" id="courseMainTabContent">
|
||||
<div t-att-class="'tab-pane fade %s' % ('show active' if active_tab == 'home' else '')" id="home" role="tabpanel" aria-labelledby="home-tab">
|
||||
<div class="mb-2 pt-1">
|
||||
<t t-if="channel.tag_ids">
|
||||
<t t-foreach="channel.tag_ids" t-as="channel_tag">
|
||||
<span t-attf-class="badge o_wslides_channel_tag #{'o_tag_color_'+str(channel_tag.color)}" t-esc="channel_tag.name"/>
|
||||
</t>
|
||||
</t>
|
||||
<a t-if="channel.can_upload"
|
||||
class="o_wslides_js_channel_tag_add border badge badge-light font-weight-normal py-1 m-1"
|
||||
role="button"
|
||||
aria-label="Add Tag"
|
||||
href="#"
|
||||
t-att-data-channel-id="channel.id"
|
||||
t-att-data-channel-tag-ids="channel.tag_ids.ids">
|
||||
<i class="fa fa-plus mr-1"/><span>Add Tag</span>
|
||||
</a>
|
||||
</div>
|
||||
<t t-if="channel.channel_type == 'training'" t-call="website_slides.course_slides_list"/>
|
||||
</div>
|
||||
<div t-if="channel.allow_comment" t-att-class="'tab-pane fade %s' % ('show active' if active_tab == 'review' else '')" id="review" role="tabpanel" aria-labelledby="review-tab">
|
||||
@@ -572,21 +588,42 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div t-if="channel.can_upload" class="text-right container pb-2">
|
||||
<a class="btn btn-primary py-1 o_wslides_js_slide_upload"
|
||||
title="Upload Presentation" role="button"
|
||||
aria-label="Upload Presentation" href="#"
|
||||
t-att-data-channel-id="channel.id"
|
||||
t-att-data-can-upload="channel.can_upload"
|
||||
t-att-data-can-publish="channel.can_publish">
|
||||
<i class="fa fa-cloud-upload mr-1"/>Upload new content
|
||||
</a>
|
||||
<a class="btn btn-secondary py-1 o_wslides_js_slide_section_add"
|
||||
title="Add Section" role="button"
|
||||
aria-label="Add Section" href="#"
|
||||
t-att-channel_id="channel.id">
|
||||
<i class="fa fa-folder-o mr-1"/>Add a section
|
||||
</a>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="mb-2 pt-1 text-left col">
|
||||
<t t-if="channel.tag_ids">
|
||||
<t t-foreach="channel.tag_ids" t-as="channel_tag">
|
||||
<span t-attf-class="badge o_wslides_channel_tag #{'o_tag_color_'+str(channel_tag.color)}" t-esc="channel_tag.name"/>
|
||||
</t>
|
||||
</t>
|
||||
<a t-if="channel.can_upload"
|
||||
class="o_wslides_js_channel_tag_add border badge badge-light font-weight-normal py-1 m-1"
|
||||
role="button"
|
||||
aria-label="Add Tag"
|
||||
href="#"
|
||||
t-att-data-channel-id="channel.id"
|
||||
t-att-data-channel-tag-ids="channel.tag_ids.ids">
|
||||
<i class="fa fa-plus mr-1"/><span>Add Tag</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div t-if="channel.can_upload" class="text-right pb-2 col-auto">
|
||||
<a class="btn btn-primary py-1 o_wslides_js_slide_upload"
|
||||
title="Upload Presentation" role="button"
|
||||
aria-label="Upload Presentation" href="#"
|
||||
t-att-data-channel-id="channel.id"
|
||||
t-att-data-can-upload="channel.can_upload"
|
||||
t-att-data-can-publish="channel.can_publish">
|
||||
<i class="fa fa-cloud-upload mr-1"/>Upload new content
|
||||
</a>
|
||||
<a class="btn btn-secondary py-1 o_wslides_js_slide_section_add"
|
||||
title="Add Section" role="button"
|
||||
aria-label="Add Section" href="#"
|
||||
t-att-channel_id="channel.id">
|
||||
<i class="fa fa-folder-o mr-1"/>Add a section
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Featured lesson -->
|
||||
<t t-call="website_slides.course_promoted_slide"/>
|
||||
|
||||
@@ -360,7 +360,12 @@
|
||||
<div class="font-weight-light o_wslides_desc_truncate_3" t-field="channel.description_short"/>
|
||||
<div t-if="channel.tag_ids" class="mt-2 pt-1 o_wslides_desc_truncate_2">
|
||||
<t t-foreach="channel.tag_ids" t-as="tag">
|
||||
<a t-att-href="'/slides/all?%s' % keep_query('*', tags=str((tag | search_tags).ids))" t-attf-class="badge #{'badge-primary' if tag in search_tags else 'badge-light'}" t-esc="tag.name"/>
|
||||
<t t-if="search_tags">
|
||||
<a t-att-href="'/slides/all?%s' % keep_query('*', tags=str((tag | search_tags).ids))" t-attf-class="badge #{'badge-primary' if tag in search_tags else 'o_wslides_channel_tag o_tag_color_0'}" t-esc="tag.name"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<a t-att-href="'/slides/all?%s' % keep_query('*', tags=str((tag | search_tags).ids))" t-attf-class="badge o_wslides_channel_tag #{'o_tag_color_'+str(tag.color)}" t-esc="tag.name"/>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
|
||||
<div class="overflow-hidden mb-1" style="height:24px">
|
||||
<t t-foreach="course.channel_id.tag_ids" t-as="tag">
|
||||
<a t-att-href="'/slides/all?channel_tag_id=%s' % tag.id" class="badge badge-light" t-esc="tag.name"/>
|
||||
<a t-att-href="'/slides/all?channel_tag_id=%s' % tag.id" t-attf-class="badge o_wslides_channel_tag #{'o_tag_color_'+str(tag.color)}" t-esc="tag.name"/>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user