[MERGE] website_slides: clean controllers and apply various fixes
Purpose of this merge is to perform some code cleaning in eLearning
controllers and demo data. There are also some fixes coming from first
user testing of eLearning. See sub commits for more details.
Merge linked to task ID 1941250 ("fix all the eLearning").
closes odoo/odoo#31512
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -13,6 +13,12 @@
|
||||
<record id="rank_newbie" model="gamification.karma.rank">
|
||||
<field name="name">Newbie</field>
|
||||
<field name="description" type="html"><p>You just began the adventure! Welcome!</p></field>
|
||||
<field name="description_motivational" type="html">
|
||||
<div class="media align-items-center">
|
||||
<div class="media-body">Reach the next rank to start conquering the world!</div>
|
||||
<img class="ml-3 img img-fluid" style="max-height: 72px;" src="/gamification/static/img/rank_misc_world.png"/>
|
||||
</div>
|
||||
</field>
|
||||
<field name="karma_min">1</field>
|
||||
<field name="image" type="base64" file="gamification/static/img/rank_newbie_badge.svg"/>
|
||||
</record>
|
||||
|
||||
@@ -47,9 +47,10 @@ class Users(models.Model):
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
result = super(Users, self).write(vals)
|
||||
if 'karma' in vals:
|
||||
self._recompute_rank()
|
||||
return super(Users, self).write(vals)
|
||||
return result
|
||||
|
||||
@api.multi
|
||||
def add_karma(self, karma):
|
||||
@@ -72,13 +73,16 @@ class Users(models.Model):
|
||||
self.env['gamification.karma.rank'].search([], order="karma_min DESC")]
|
||||
for user in self:
|
||||
old_rank = user.rank_id
|
||||
for i in range(0, len(ranks)):
|
||||
if user.karma >= ranks[i]['karma_min']:
|
||||
user.write({
|
||||
'rank_id': ranks[i]['rank'].id,
|
||||
'next_rank_id': ranks[i - 1]['rank'].id if i > 0 else False
|
||||
})
|
||||
break
|
||||
if user.karma == 0 and ranks:
|
||||
user.write({'next_rank_id': ranks[-1]['rank'].id})
|
||||
else:
|
||||
for i in range(0, len(ranks)):
|
||||
if user.karma >= ranks[i]['karma_min']:
|
||||
user.write({
|
||||
'rank_id': ranks[i]['rank'].id,
|
||||
'next_rank_id': ranks[i - 1]['rank'].id if 0 < i else False
|
||||
})
|
||||
break
|
||||
if old_rank != user.rank_id:
|
||||
user._rank_changed()
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
@@ -218,3 +218,8 @@ img.o_forum_avatar_big {
|
||||
min-height: 75px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_wforum_elearning_navtabs_container {
|
||||
height: $owprofile-tabs-height;
|
||||
background-color: #66445e;
|
||||
}
|
||||
|
||||
@@ -178,8 +178,8 @@
|
||||
</div>
|
||||
<button class="btn btn-sm d-md-none bg-white border" type="button" data-toggle="collapse" data-target="#o_wprofile_sidebar_collapse" aria-expanded="false" aria-controls="o_wprofile_sidebar_collapse">More info</button>
|
||||
</div>
|
||||
<div t-if="user.rank_id" class="collapse d-md-block" id="o_wprofile_sidebar_collapse">
|
||||
<div class="mb-3">
|
||||
<div class="collapse d-md-block" id="o_wprofile_sidebar_collapse">
|
||||
<div t-if="user.next_rank_id" class="mb-3">
|
||||
<small class="font-weight-bold mt-1">Next rank:</small>
|
||||
<t t-call="website_profile.profile_next_rank_card">
|
||||
<t t-set="img_max_width">40%</t>
|
||||
@@ -228,9 +228,14 @@
|
||||
</template>
|
||||
|
||||
<template id="profile_next_rank_card" name="Profile Next Rank Card">
|
||||
<div class="o_wprofile_progress_circle">
|
||||
<div t-if="user.next_rank_id" class="o_wprofile_progress_circle">
|
||||
<svg viewBox="0 0 36 36" class="o_pc_circular_chart">
|
||||
<t t-set="user_points" t-value="int(100*(user.karma - user.rank_id.karma_min)/(user.next_rank_id.karma_min - user.rank_id.karma_min))"/>
|
||||
<t t-if="(user.next_rank_id.karma_min - user.rank_id.karma_min) > 0">
|
||||
<t t-set="user_points" t-value="int(100*(user.karma - user.rank_id.karma_min)/(user.next_rank_id.karma_min - user.rank_id.karma_min))"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-set="user_points" t-value="0"/>
|
||||
</t>
|
||||
<path class="o_pc_circle_bg" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
|
||||
<path class="o_pc_circle" t-attf-stroke-dasharray="#{user_points}, 100" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" stroke="url(#gradient)" />
|
||||
<linearGradient id="gradient">
|
||||
@@ -254,16 +259,18 @@
|
||||
<template id="user_badges" name="User Bagdes">
|
||||
<div t-if="user.badge_ids" class="row mx-n1">
|
||||
<div class="col px-1 mb-2 col-xl-4" t-foreach="user.badge_ids" t-as="badge">
|
||||
<div class="card">
|
||||
<div class="card-body p-2 pr-3">
|
||||
<div class="media align-items-center">
|
||||
<img height="38" t-attf-src="/web/image/gamification.badge/#{badge.badge_id.id}/image_small" class="mr-2"/>
|
||||
<div class="media-body text-nowrap">
|
||||
<h6 class="my-0" t-field="badge.badge_id.name"/>
|
||||
<t t-if="badge.badge_id.website_published">
|
||||
<div class="card">
|
||||
<div class="card-body p-2 pr-3">
|
||||
<div class="media align-items-center">
|
||||
<img height="38" t-attf-src="/web/image/gamification.badge/#{badge.badge_id.id}/image_small" class="mr-2"/>
|
||||
<div class="media-body text-nowrap">
|
||||
<h6 class="my-0" t-field="badge.badge_id.name"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3" t-if="not user.badge_ids">
|
||||
|
||||
@@ -37,6 +37,9 @@ class WebsiteSlides(WebsiteProfile):
|
||||
if not qs or qs.lower() in loc:
|
||||
yield {'loc': loc}
|
||||
|
||||
# SLIDE UTILITIES
|
||||
# --------------------------------------------------
|
||||
|
||||
def _fetch_slide(self, slide_id):
|
||||
slide = request.env['slide.slide'].browse(int(slide_id)).exists()
|
||||
if not slide:
|
||||
@@ -49,7 +52,7 @@ class WebsiteSlides(WebsiteProfile):
|
||||
return {'slide': slide}
|
||||
|
||||
def _set_viewed_slide(self, slide):
|
||||
if request.env.user._is_public() or slide.is_preview or not slide.channel_id.is_member:
|
||||
if request.env.user._is_public() or not slide.website_published or not slide.channel_id.is_member:
|
||||
viewed_slides = request.session.setdefault('viewed_slides', list())
|
||||
if slide.id not in viewed_slides:
|
||||
slide.sudo().public_views += 1
|
||||
@@ -65,26 +68,42 @@ class WebsiteSlides(WebsiteProfile):
|
||||
return True
|
||||
|
||||
def _get_slide_detail(self, slide):
|
||||
most_viewed_slides = slide._get_most_viewed_slides(self.SLIDES_PER_ASIDE)
|
||||
related_slides = slide._get_related_slides(self.SLIDES_PER_ASIDE)
|
||||
if slide.channel_id.channel_type == 'documentation':
|
||||
most_viewed_slides = slide._get_most_viewed_slides(self.SLIDES_PER_ASIDE)
|
||||
related_slides = slide._get_related_slides(self.SLIDES_PER_ASIDE)
|
||||
uncategorized_slides = request.env['slide.slide']
|
||||
else:
|
||||
most_viewed_slides, related_slides = request.env['slide.slide'], request.env['slide.slide']
|
||||
uncategorized_slides = slide.channel_id.slide_ids.filtered(lambda slide: not slide.category_id)
|
||||
|
||||
channel_slides_ids = slide.channel_id.slide_ids.ids
|
||||
slide_index = channel_slides_ids.index(slide.id)
|
||||
previous_slide = slide.channel_id.slide_ids[slide_index-1] if slide_index > 0 else None
|
||||
next_slide = slide.channel_id.slide_ids[slide_index+1] if slide_index < len(channel_slides_ids) - 1 else None
|
||||
|
||||
values = {
|
||||
# slide
|
||||
'slide': slide,
|
||||
'most_viewed_slides': most_viewed_slides,
|
||||
'related_slides': related_slides,
|
||||
'previous_slide': previous_slide,
|
||||
'next_slide': next_slide,
|
||||
'uncategorized_slides': uncategorized_slides,
|
||||
# user
|
||||
'user': request.env.user,
|
||||
'user_progress': self._get_user_progress(slide.channel_id)['user_progress'],
|
||||
'is_public_user': request.website.is_public_user(),
|
||||
# rating and comments
|
||||
'comments': slide.website_message_ids or [],
|
||||
'user_progress': {}
|
||||
}
|
||||
if slide.channel_id.channel_type == "training":
|
||||
channel_slides_ids = slide.channel_id.slide_ids.ids
|
||||
slide_index = channel_slides_ids.index(slide.id)
|
||||
previous_slide = slide.channel_id.slide_ids[slide_index-1] if slide_index > 0 else None
|
||||
next_slide = slide.channel_id.slide_ids[slide_index+1] if slide_index < len(channel_slides_ids) - 1 else None
|
||||
|
||||
# allow rating and comments
|
||||
if slide.channel_id.allow_comment:
|
||||
values.update({
|
||||
'previous_slide': slug(previous_slide) if previous_slide else "",
|
||||
'next_slide': slug(next_slide) if next_slide else ""
|
||||
'message_post_hash': slide._generate_signed_token(request.env.user.partner_id.id),
|
||||
'message_post_pid': request.env.user.partner_id.id,
|
||||
})
|
||||
|
||||
return values
|
||||
|
||||
def _get_quiz_points(self, slide, attempt_count):
|
||||
@@ -164,7 +183,7 @@ class WebsiteSlides(WebsiteProfile):
|
||||
return domain
|
||||
|
||||
# --------------------------------------------------
|
||||
# MAIN / SEARCH
|
||||
# SLIDE.CHANNEL MAIN / SEARCH
|
||||
# --------------------------------------------------
|
||||
|
||||
@http.route('/slides', type='http', auth="public", website=True)
|
||||
@@ -173,7 +192,7 @@ class WebsiteSlides(WebsiteProfile):
|
||||
domain = request.website.website_domain()
|
||||
channels_all = request.env['slide.channel'].search(domain)
|
||||
if not request.env.user._is_public():
|
||||
channels_my = channels_all.filtered(lambda channel: channel.completion > 0).sorted('completion', reverse=True)[:3]
|
||||
channels_my = channels_all.filtered(lambda channel: channel.is_member).sorted('completion', reverse=True)[:3]
|
||||
else:
|
||||
channels_my = request.env['slide.channel']
|
||||
channels_popular = channels_all.sorted('total_votes', reverse=True)[:3]
|
||||
@@ -416,7 +435,10 @@ class WebsiteSlides(WebsiteProfile):
|
||||
|
||||
return request.render('website_slides.course_main', values)
|
||||
|
||||
@http.route(['/slides/channel/add'], type='http', auth='user', methods=['POST'], website=True)
|
||||
# SLIDE.CHANNEL UTILS
|
||||
# --------------------------------------------------
|
||||
|
||||
@http.route('/slides/channel/add', type='http', auth='user', methods=['POST'], website=True)
|
||||
def slide_channel_create(self, *args, **kw):
|
||||
channel = request.env['slide.channel'].create(self._slide_channel_prepare_values(**kw))
|
||||
return werkzeug.utils.redirect("/slides/%s" % (slug(channel)))
|
||||
@@ -437,29 +459,63 @@ class WebsiteSlides(WebsiteProfile):
|
||||
'allow_comment': bool(kw.get('allow_comment')),
|
||||
}
|
||||
|
||||
@http.route('/slides/channel/enroll', type='http', auth='public', website=True)
|
||||
def slide_channel_join_http(self, channel_id):
|
||||
# TDE FIXME: why 2 routes ?
|
||||
if not request.website.is_public_user():
|
||||
channel = request.env['slide.channel'].browse(int(channel_id))
|
||||
channel.action_add_member()
|
||||
return werkzeug.utils.redirect("/slides/%s" % (slug(channel)))
|
||||
|
||||
@http.route(['/slides/channel/join'], type='json', auth='public', website=True)
|
||||
def slide_channel_join(self, channel_id):
|
||||
if request.website.is_public_user():
|
||||
return {'error': 'public_user'}
|
||||
success = request.env['slide.channel'].browse(channel_id).action_add_member()
|
||||
if not success:
|
||||
return {'error': 'join_done'}
|
||||
return success
|
||||
|
||||
@http.route('/slides/channel/resequence', type="json", website=True, auth="user")
|
||||
def resequence_slides(self, channel_id, slides_data):
|
||||
"""" Reorder the slides within the channel by reassigning their 'sequence' field.
|
||||
This method also handles slides that are put in a new category (or uncategorized). """
|
||||
channel = request.env['slide.channel'].browse(int(channel_id))
|
||||
if not channel.can_publish:
|
||||
return {'error': 'Only the publishers of the channel can edit it'}
|
||||
|
||||
slides = request.env['slide.slide'].search([
|
||||
('id', 'in', [int(key) for key in slides_data.keys()]),
|
||||
('channel_id', '=', channel.id)
|
||||
])
|
||||
|
||||
for slide in slides:
|
||||
slide_key = str(slide.id)
|
||||
slide.sequence = slides_data[slide_key]['sequence']
|
||||
slide.category_id = slides_data[slide_key]['category_id'] if 'category_id' in slides_data[slide_key] else False
|
||||
|
||||
@http.route(['/slides/channel/tag/search_read'], type='json', auth='user', methods=['POST'], website=True)
|
||||
def slide_channel_tag_search_read(self, fields, domain):
|
||||
can_create = request.env['slide.channel.tag'].check_access_rights('create', raise_exception=False)
|
||||
return {
|
||||
'read_results': request.env['slide.channel.tag'].search_read(domain, fields),
|
||||
'can_create': can_create,
|
||||
}
|
||||
|
||||
# --------------------------------------------------
|
||||
# SLIDE.SLIDE CONTOLLERS
|
||||
# SLIDE.SLIDE MAIN / SEARCH
|
||||
# --------------------------------------------------
|
||||
|
||||
@http.route('''/slides/slide/<model("slide.slide", "[('website_id', 'in', (False, current_website_id))]"):slide>''', type='http', auth="public", website=True)
|
||||
def slide_view(self, slide, **kwargs):
|
||||
if not slide.channel_id.can_access_from_current_website():
|
||||
raise werkzeug.exceptions.NotFound()
|
||||
self._set_viewed_slide(slide)
|
||||
|
||||
self._set_viewed_slide(slide)
|
||||
values = self._get_slide_detail(slide)
|
||||
# allow rating and comments
|
||||
if slide.channel_id.allow_comment:
|
||||
values.update({
|
||||
'message_post_hash': slide._generate_signed_token(request.env.user.partner_id.id),
|
||||
'message_post_pid': request.env.user.partner_id.id,
|
||||
})
|
||||
self._set_viewed_slide(slide)
|
||||
if slide.channel_id.channel_type == "training":
|
||||
values.update(self._get_user_progress(slide.channel_id))
|
||||
values['uncategorized_slides'] = slide.channel_id.slide_ids.filtered(lambda slide: not slide.category_id)
|
||||
if 'fullscreen' in kwargs:
|
||||
return request.render("website_slides.slide_fullscreen", values)
|
||||
|
||||
if 'fullscreen' in kwargs:
|
||||
return request.render("website_slides.slide_fullscreen", values)
|
||||
return request.render("website_slides.slide_detail_view", values)
|
||||
|
||||
@http.route('''/slides/slide/<model("slide.slide"):slide>/pdf_content''',
|
||||
@@ -500,6 +556,9 @@ class WebsiteSlides(WebsiteProfile):
|
||||
response.status_code = status
|
||||
return response
|
||||
|
||||
# SLIDE.SLIDE UTILS
|
||||
# --------------------------------------------------
|
||||
|
||||
@http.route('/slide/html_content/get', type="json", auth="public", website=True)
|
||||
def get_html_content(self, slide_id):
|
||||
slide = request.env['slide.slide'].browse(slide_id)
|
||||
@@ -507,7 +566,61 @@ class WebsiteSlides(WebsiteProfile):
|
||||
'html_content': slide.html_content
|
||||
}
|
||||
|
||||
#SLIDE QUIZ CONTROLLERS
|
||||
@http.route('/slides/slide/<model("slide.slide"):slide>/set_completed', website=True, type="http", auth="user")
|
||||
def slide_set_completed_and_redirect(self, slide, next_slide=None):
|
||||
self._set_completed_slide(slide)
|
||||
return werkzeug.utils.redirect("/slides/slide/%s" % (next_slide if next_slide else slide.id))
|
||||
|
||||
@http.route('/slides/slide/set_completed', website=True, type="json", auth="public")
|
||||
def slide_set_completed(self, slide_id):
|
||||
if request.website.is_public_user():
|
||||
return {'error': 'public_user'}
|
||||
fetch_res = self._fetch_slide(slide_id)
|
||||
if fetch_res.get('error'):
|
||||
return fetch_res
|
||||
self._set_completed_slide(fetch_res['slide'])
|
||||
return {
|
||||
'channel_completion': fetch_res['slide'].channel_id.completion
|
||||
}
|
||||
|
||||
@http.route('/slides/slide/like', type='json', auth="public", website=True)
|
||||
def slide_like(self, slide_id, upvote):
|
||||
if request.website.is_public_user():
|
||||
return {'error': 'public_user'}
|
||||
slide_partners = request.env['slide.slide.partner'].sudo().search([
|
||||
('slide_id', '=', slide_id),
|
||||
('partner_id', '=', request.env.user.partner_id.id)
|
||||
])
|
||||
if (upvote and slide_partners.vote == 1) or (not upvote and slide_partners.vote == -1):
|
||||
return {'error': 'vote_done'}
|
||||
slide = request.env['slide.slide'].browse(int(slide_id))
|
||||
if upvote:
|
||||
slide.action_like()
|
||||
else:
|
||||
slide.action_dislike()
|
||||
slide.invalidate_cache()
|
||||
return slide.read(['likes', 'dislikes', 'user_vote'])[0]
|
||||
|
||||
@http.route('/slides/slide/archive', type='json', auth='user', website=True)
|
||||
def slide_archive(self, slide_id):
|
||||
""" This route allows channel publishers to archive slides.
|
||||
It has to be done in sudo mode since only website_publishers can write on slides in ACLs """
|
||||
slide = request.env['slide.slide'].browse(int(slide_id))
|
||||
if slide.channel_id.can_publish:
|
||||
slide.sudo().active = False
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@http.route(['/slides/slide/send_share_email'], type='json', auth='user', website=True)
|
||||
def slide_send_share_email(self, slide_id, email):
|
||||
slide = request.env['slide.slide'].browse(int(slide_id))
|
||||
result = slide._send_share_email(email)
|
||||
return result
|
||||
|
||||
# --------------------------------------------------
|
||||
# QUIZZ SECTION
|
||||
# --------------------------------------------------
|
||||
|
||||
@http.route('/slide/quiz/get', type="json", auth="public", website=True)
|
||||
def get_quiz(self, **kw):
|
||||
@@ -573,62 +686,6 @@ class WebsiteSlides(WebsiteProfile):
|
||||
'error': "You already passed this quiz"
|
||||
}
|
||||
|
||||
# --------------------------------------------------
|
||||
# SLIDE.SLIDE TOOLS CONTOLLERS
|
||||
# --------------------------------------------------
|
||||
|
||||
@http.route('/slides/slide/<model("slide.slide"):slide>/set_completed', website=True, type="http", auth="user")
|
||||
def slide_set_completed_and_redirect(self, slide, next_slide=None):
|
||||
self._set_completed_slide(slide)
|
||||
return werkzeug.utils.redirect("/slides/slide/%s" % (next_slide if next_slide else slide.id))
|
||||
|
||||
@http.route('/slides/slide/set_completed', website=True, type="json", auth="public")
|
||||
def slide_set_completed(self, slide_id):
|
||||
if request.website.is_public_user():
|
||||
return {'error': 'public_user'}
|
||||
fetch_res = self._fetch_slide(slide_id)
|
||||
if fetch_res.get('error'):
|
||||
return fetch_res
|
||||
self._set_completed_slide(fetch_res['slide'])
|
||||
return {
|
||||
'channel_completion': fetch_res['slide'].channel_id.completion
|
||||
}
|
||||
|
||||
@http.route('/slides/slide/like', type='json', auth="public", website=True)
|
||||
def slide_like(self, slide_id, upvote):
|
||||
if request.website.is_public_user():
|
||||
return {'error': 'public_user'}
|
||||
slide_partners = request.env['slide.slide.partner'].sudo().search([
|
||||
('slide_id', '=', slide_id),
|
||||
('partner_id', '=', request.env.user.partner_id.id)
|
||||
])
|
||||
if (upvote and slide_partners.vote == 1) or (not upvote and slide_partners.vote == -1):
|
||||
return {'error': 'vote_done'}
|
||||
slide = request.env['slide.slide'].browse(int(slide_id))
|
||||
if upvote:
|
||||
slide.action_like()
|
||||
else:
|
||||
slide.action_dislike()
|
||||
slide.invalidate_cache()
|
||||
return slide.read(['likes', 'dislikes', 'user_vote'])[0]
|
||||
|
||||
@http.route('/slides/slide/archive', type='json', auth='user', website=True)
|
||||
def slide_archive(self, slide_id):
|
||||
""" This route allows channel publishers to archive slides.
|
||||
It has to be done in sudo mode since only website_publishers can write on slides in ACLs """
|
||||
slide = request.env['slide.slide'].browse(int(slide_id))
|
||||
if slide.channel_id.can_publish:
|
||||
slide.sudo().active = False
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@http.route(['/slides/slide/send_share_email'], type='json', auth='user', website=True)
|
||||
def slide_send_share_email(self, slide_id, email):
|
||||
slide = request.env['slide.slide'].browse(int(slide_id))
|
||||
result = slide._send_share_email(email)
|
||||
return result
|
||||
|
||||
# --------------------------------------------------
|
||||
# CATEGORY MANAGEMENT
|
||||
# --------------------------------------------------
|
||||
@@ -663,44 +720,9 @@ class WebsiteSlides(WebsiteProfile):
|
||||
return werkzeug.utils.redirect("/slides/%s" % (slug(channel)))
|
||||
|
||||
# --------------------------------------------------
|
||||
# TOOLS
|
||||
# SLIDE.UPLOAD
|
||||
# --------------------------------------------------
|
||||
|
||||
@http.route(['/slides/channel/enroll'], type='http', auth='public', website=True)
|
||||
def slide_channel_join_http(self, channel_id):
|
||||
# TDE FIXME: why 2 routes ?
|
||||
if not request.website.is_public_user():
|
||||
channel = request.env['slide.channel'].browse(int(channel_id))
|
||||
channel.action_add_member()
|
||||
return werkzeug.utils.redirect("/slides/%s" % (slug(channel)))
|
||||
|
||||
@http.route(['/slides/channel/join'], type='json', auth='public', website=True)
|
||||
def slide_channel_join(self, channel_id):
|
||||
if request.website.is_public_user():
|
||||
return {'error': 'public_user'}
|
||||
success = request.env['slide.channel'].browse(channel_id).action_add_member()
|
||||
if not success:
|
||||
return {'error': 'join_done'}
|
||||
return success
|
||||
|
||||
@http.route('/slides/channel/resequence', type="json", website=True, auth="user")
|
||||
def resequence_slides(self, channel_id, slides_data):
|
||||
"""" Reorder the slides within the channel by reassigning their 'sequence' field.
|
||||
This method also handles slides that are put in a new category (or uncategorized). """
|
||||
channel = request.env['slide.channel'].browse(int(channel_id))
|
||||
if not channel.can_publish:
|
||||
return {'error': 'Only the publishers of the channel can edit it'}
|
||||
|
||||
slides = request.env['slide.slide'].search([
|
||||
('id', 'in', [int(key) for key in slides_data.keys()]),
|
||||
('channel_id', '=', channel.id)
|
||||
])
|
||||
|
||||
for slide in slides:
|
||||
slide_key = str(slide.id)
|
||||
slide.sequence = slides_data[slide_key]['sequence']
|
||||
slide.category_id = slides_data[slide_key]['category_id'] if 'category_id' in slides_data[slide_key] else False
|
||||
|
||||
@http.route(['/slides/prepare_preview'], type='json', auth='user', methods=['POST'], website=True)
|
||||
def prepare_preview(self, **data):
|
||||
Slide = request.env['slide.slide']
|
||||
@@ -780,14 +802,6 @@ class WebsiteSlides(WebsiteProfile):
|
||||
return ['name', 'url', 'tag_ids', 'slide_type', 'channel_id', 'is_preview',
|
||||
'mime_type', 'datas', 'description', 'image', 'index_content', 'website_published']
|
||||
|
||||
@http.route(['/slides/channel/tag/search_read'], type='json', auth='user', methods=['POST'], website=True)
|
||||
def slide_channel_tag_search_read(self, fields, domain):
|
||||
can_create = request.env['slide.channel.tag'].check_access_rights('create', raise_exception=False)
|
||||
return {
|
||||
'read_results': request.env['slide.channel.tag'].search_read(domain, fields),
|
||||
'can_create': can_create,
|
||||
}
|
||||
|
||||
@http.route(['/slides/tag/search_read'], type='json', auth='user', methods=['POST'], website=True)
|
||||
def slide_tag_search_read(self, fields, domain):
|
||||
can_create = request.env['slide.tag'].check_access_rights('create', raise_exception=False)
|
||||
@@ -799,6 +813,7 @@ class WebsiteSlides(WebsiteProfile):
|
||||
# --------------------------------------------------
|
||||
# EMBED IN THIRD PARTY WEBSITES
|
||||
# --------------------------------------------------
|
||||
|
||||
@http.route('/slides/embed/<int:slide_id>', type='http', auth='public', website=True, sitemap=False)
|
||||
def slides_embed(self, slide_id, page="1", **kw):
|
||||
# Note : don't use the 'model' in the route (use 'slide_id'), otherwise if public cannot access the embedded
|
||||
|
||||
@@ -30,14 +30,14 @@
|
||||
<field name="public_views">5</field>
|
||||
<field name="completion_time">1</field>
|
||||
<field name="tag_ids" eval="[(4, ref('website_slides.slide_tag_demo_colorful')), (4, ref('website_slides.slide_tag_demo_theory'))]"/>
|
||||
<field name="description">A summary of know-how: how and what.</field>
|
||||
<field name="description">Interesting informations about home gardening. Keep it close !</field>
|
||||
</record>
|
||||
|
||||
<!-- CHANNEL 1: Taking care of Trees -->
|
||||
<!-- ================================================== -->
|
||||
<record id="slide_slide_demo_1_0" model="slide.slide">
|
||||
<field name="name">Tree Infographic</field>
|
||||
<field name="sequence">1</field>
|
||||
<field name="sequence">1</field>
|
||||
<field name="image" type="base64" file="website_slides/static/src/img/slide_demo_tree_infographic_1.jpg"/>
|
||||
<field name="slide_type">infographic</field>
|
||||
<field name="channel_id" ref="website_slides.slide_channel_demo_1_gard1"/>
|
||||
@@ -152,6 +152,16 @@
|
||||
<field name="tag_ids" eval="[(4, ref('website_slides.slide_tag_demo_colorful'))]"/>
|
||||
<field name="description">We had a little chat with Harry Potted, sure he had interesting things to say !</field>
|
||||
</record>
|
||||
<record id="slide_slide_demo_1_4_link_0" model="slide.slide.link">
|
||||
<field name="name">Know More Link 1</field>
|
||||
<field name="link">http://www.example.com</field>
|
||||
<field name="slide_id" ref="slide_slide_demo_1_4"/>
|
||||
</record>
|
||||
<record id="slide_slide_demo_1_4_link_1" model="slide.slide.link">
|
||||
<field name="name">Know More Link 2</field>
|
||||
<field name="link">http://www.example.com</field>
|
||||
<field name="slide_id" ref="slide_slide_demo_1_4"/>
|
||||
</record>
|
||||
<record id="slide_slide_demo_1_4_question_0" model="slide.question">
|
||||
<field name="question">Do you think Harry Potted has a good name ?</field>
|
||||
<field name="slide_id" ref="slide_slide_demo_1_4"/>
|
||||
@@ -166,6 +176,25 @@
|
||||
<field name="is_correct" eval="False"/>
|
||||
<field name="question_id" ref="slide_slide_demo_1_4_question_0"/>
|
||||
</record>
|
||||
<record id="slide_slide_demo_1_4_question_1" model="slide.question">
|
||||
<field name="question">Did you read the whole article ?</field>
|
||||
<field name="slide_id" ref="slide_slide_demo_1_4"/>
|
||||
</record>
|
||||
<record id="slide_slide_demo_1_4_question_1_0" model="slide.answer">
|
||||
<field name="text_value">Yes</field>
|
||||
<field name="is_correct" eval="True"/>
|
||||
<field name="question_id" ref="slide_slide_demo_1_4_question_1"/>
|
||||
</record>
|
||||
<record id="slide_slide_demo_1_4_question_1_1" model="slide.answer">
|
||||
<field name="text_value">No</field>
|
||||
<field name="is_correct" eval="False"/>
|
||||
<field name="question_id" ref="slide_slide_demo_1_4_question_1"/>
|
||||
</record>
|
||||
<record id="slide_slide_demo_1_4_question_1_2" model="slide.answer">
|
||||
<field name="text_value">What was the question again ?</field>
|
||||
<field name="is_correct" eval="False"/>
|
||||
<field name="question_id" ref="slide_slide_demo_1_4_question_1"/>
|
||||
</record>
|
||||
<record id="slide_slide_demo_1_5" model="slide.slide">
|
||||
<field name="name">3 Main Methodologies</field>
|
||||
<field name="sequence">6</field>
|
||||
@@ -204,29 +233,48 @@
|
||||
<field name="description">A summary of know-how: how and what.</field>
|
||||
</record>
|
||||
<record id="slide_slide_demo_2_0_link_0" model="slide.slide.link">
|
||||
<field name="name">Example Link 1</field>
|
||||
<field name="name">Trees Classification Link 1</field>
|
||||
<field name="link">http://www.example.com</field>
|
||||
<field name="slide_id" ref="slide_slide_demo_2_0"/>
|
||||
</record>
|
||||
<record id="slide_slide_demo_2_0_link_1" model="slide.slide.link">
|
||||
<field name="name">Example Link 2</field>
|
||||
<field name="name">Trees Classification Link 2</field>
|
||||
<field name="link">http://www.example.com</field>
|
||||
<field name="slide_id" ref="slide_slide_demo_2_0"/>
|
||||
</record>
|
||||
<record id="slide_slide_demo_2_0_question_0" model="slide.question">
|
||||
<field name="question">Do you want to reply correctly ?</field>
|
||||
<field name="question">Do you make beams out of lemon trees ?</field>
|
||||
<field name="slide_id" ref="slide_slide_demo_2_0"/>
|
||||
</record>
|
||||
<record id="slide_slide_demo_2_0_question_0_0" model="slide.answer">
|
||||
<field name="text_value">Yes</field>
|
||||
<field name="is_correct" eval="True"/>
|
||||
<field name="is_correct" eval="False"/>
|
||||
<field name="question_id" ref="slide_slide_demo_2_0_question_0"/>
|
||||
</record>
|
||||
<record id="slide_slide_demo_2_0_question_0_1" model="slide.answer">
|
||||
<field name="text_value">No</field>
|
||||
<field name="is_correct" eval="False"/>
|
||||
<field name="is_correct" eval="True"/>
|
||||
<field name="question_id" ref="slide_slide_demo_2_0_question_0"/>
|
||||
</record>
|
||||
<record id="slide_slide_demo_2_0_question_1" model="slide.question">
|
||||
<field name="question">Do you make lemons out of beams ?</field>
|
||||
<field name="slide_id" ref="slide_slide_demo_2_0"/>
|
||||
</record>
|
||||
<record id="slide_slide_demo_2_0_question_1_0" model="slide.answer">
|
||||
<field name="text_value">Yes</field>
|
||||
<field name="is_correct" eval="False"/>
|
||||
<field name="question_id" ref="slide_slide_demo_2_0_question_1"/>
|
||||
</record>
|
||||
<record id="slide_slide_demo_2_0_question_1_1" model="slide.answer">
|
||||
<field name="text_value">No</field>
|
||||
<field name="is_correct" eval="True"/>
|
||||
<field name="question_id" ref="slide_slide_demo_2_0_question_1"/>
|
||||
</record>
|
||||
<record id="slide_slide_demo_2_0_question_1_2" model="slide.answer">
|
||||
<field name="text_value">And also bananas</field>
|
||||
<field name="is_correct" eval="False"/>
|
||||
<field name="question_id" ref="slide_slide_demo_2_0_question_1"/>
|
||||
</record>
|
||||
<record id="slide_slide_demo_2_1" model="slide.slide">
|
||||
<field name="name">A Mighty Forest from Ages</field>
|
||||
<field name="sequence">2</field>
|
||||
|
||||
@@ -33,6 +33,15 @@
|
||||
<field name="partner_id" ref="base.partner_demo"/>
|
||||
<field name="vote">1</field>
|
||||
</record>
|
||||
<record id="slide_channel_0_partner_demo_portal" model="slide.channel.partner">
|
||||
<field name="channel_id" ref="website_slides.channel_public"/>
|
||||
<field name="partner_id" ref="base.partner_demo_portal"/>
|
||||
</record>
|
||||
<record id="slide_slide_0_0_partner_demo_portal" model="slide.slide.partner">
|
||||
<field name="slide_id" ref="website_slides.slide_slide_demo_0_0"/>
|
||||
<field name="partner_id" ref="base.partner_demo_portal"/>
|
||||
<field name="vote">1</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- CHANNEL 1: Taking care of Trees -->
|
||||
@@ -54,26 +63,40 @@
|
||||
<record id="slide_slide_1_2_partner_admin" model="slide.slide.partner">
|
||||
<field name="slide_id" ref="website_slides.slide_slide_demo_1_2"/>
|
||||
<field name="partner_id" ref="base.partner_admin"/>
|
||||
<field name="vote">1</field>
|
||||
<field name="completed" eval="True"/>
|
||||
</record>
|
||||
<record id="slide_slide_1_4_partner_admin" model="slide.slide.partner">
|
||||
<field name="slide_id" ref="website_slides.slide_slide_demo_1_4"/>
|
||||
<record id="slide_slide_1_3_partner_admin" model="slide.slide.partner">
|
||||
<field name="slide_id" ref="website_slides.slide_slide_demo_1_3"/>
|
||||
<field name="partner_id" ref="base.partner_admin"/>
|
||||
<field name="quiz_attempts_count">2</field>
|
||||
<field name="vote">1</field>
|
||||
<field name="completed" eval="True"/>
|
||||
</record>
|
||||
<record id="slide_channel_1_partner_demo" model="slide.channel.partner">
|
||||
<field name="channel_id" ref="website_slides.slide_channel_demo_1_gard1"/>
|
||||
<field name="partner_id" ref="base.partner_demo"/>
|
||||
</record>
|
||||
<record id="slide_slide_1_0_partner_demo_portal" model="slide.slide.partner">
|
||||
<record id="slide_slide_1_0_partner_demo" model="slide.slide.partner">
|
||||
<field name="slide_id" ref="website_slides.slide_slide_demo_1_0"/>
|
||||
<field name="partner_id" ref="base.partner_demo_portal"/>
|
||||
<field name="partner_id" ref="base.partner_demo"/>
|
||||
<field name="vote">1</field>
|
||||
<field name="completed" eval="True"/>
|
||||
</record>
|
||||
<record id="slide_slide_1_1_partner_demo" model="slide.slide.partner">
|
||||
<field name="slide_id" ref="website_slides.slide_slide_demo_1_1"/>
|
||||
<field name="partner_id" ref="base.partner_demo"/>
|
||||
<field name="vote">1</field>
|
||||
</record>
|
||||
<record id="slide_channel_1_partner_demo_portal" model="slide.channel.partner">
|
||||
<field name="channel_id" ref="website_slides.slide_channel_demo_1_gard1"/>
|
||||
<field name="partner_id" ref="base.partner_demo_portal"/>
|
||||
<field name="completed" eval="True"/>
|
||||
</record>
|
||||
<record id="slide_slide_1_0_partner_demo" model="slide.slide.partner">
|
||||
<field name="slide_id" ref="website_slides.slide_slide_demo_1_0"/>
|
||||
<field name="partner_id" ref="base.partner_demo_portal"/>
|
||||
<field name="vote">1</field>
|
||||
<field name="completed" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="message_channel_1_admin" model="mail.message">
|
||||
@@ -128,6 +151,7 @@
|
||||
<field name="message_id" ref="website_slides.message_channel_1_portal"/>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- CHANNEL 2: Trees, Wood and Garden -->
|
||||
<!-- ================================================== -->
|
||||
<record id="slide_channel_2_partner_admin" model="slide.channel.partner">
|
||||
@@ -138,7 +162,6 @@
|
||||
<field name="slide_id" ref="website_slides.slide_slide_demo_2_0"/>
|
||||
<field name="partner_id" ref="base.partner_admin"/>
|
||||
<field name="vote">1</field>
|
||||
<field name="completed" eval="True"/>
|
||||
</record>
|
||||
<record id="slide_slide_2_1_partner_admin" model="slide.slide.partner">
|
||||
<field name="slide_id" ref="website_slides.slide_slide_demo_2_1"/>
|
||||
|
||||
@@ -342,17 +342,19 @@
|
||||
|
||||
<template id='slides_home_user_profile_small' name="User Profile">
|
||||
<div class="o_wslides_home_aside">
|
||||
<div class="d-flex align-items-center">
|
||||
<div t-if="user.rank_id" class="d-flex align-items-center">
|
||||
<span class="font-weight-bold text-muted mr-2">Current rank:</span>
|
||||
<img t-attf-src="/web/image/gamification.karma.rank/#{user.rank_id.id}/image_small" height="16" alt="" class="mr-1"/>
|
||||
<a href="/profile/ranks" t-field="user.rank_id"/>
|
||||
</div>
|
||||
<div class="font-weight-bold text-muted mt-1">Next rank:</div>
|
||||
<t t-call="website_profile.profile_next_rank_card">
|
||||
<t t-set="bg_class">bg-200</t>
|
||||
<t t-set="img_max_width">50%</t>
|
||||
<t t-if="user.next_rank_id">
|
||||
<div class="font-weight-bold text-muted mt-1">Next rank:</div>
|
||||
<t t-call="website_profile.profile_next_rank_card">
|
||||
<t t-set="bg_class">bg-200</t>
|
||||
<t t-set="img_max_width">50%</t>
|
||||
</t>
|
||||
<div t-field="user.next_rank_id.description_motivational"/>
|
||||
</t>
|
||||
<div t-field="user.next_rank_id.description_motivational"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<t t-call="website_slides.course_nav">
|
||||
<t t-set="channel" t-value="slide.channel_id"/>
|
||||
</t>
|
||||
<div class="wrap o_wslides_wrap">
|
||||
<div class="o_wslides_slide_header">
|
||||
<div class="o_wslides_slide_header_container">
|
||||
<div class="o_wslides_slide_header_box">
|
||||
@@ -55,13 +56,13 @@
|
||||
</div>
|
||||
<div>
|
||||
<t t-if="previous_slide">
|
||||
<a class="o_wslides_slide_button" t-attf-href="/slides/slide/#{previous_slide}">Prev</a>
|
||||
<a class="o_wslides_slide_button" t-attf-href="/slides/slide/#{slug(previous_slide)}">Prev</a>
|
||||
</t>
|
||||
<a t-if="slide.slide_type in ('webpage', 'video', 'document', 'iconographic') and not slide.question_ids and (slide.id in user_progress and not user_progress[slide.id].completed)"
|
||||
t-att-href="'/slides/slide/%s/set_completed?%s' % (slide.id, 'next_slide=%s' % next_slide if next_slide else '')"
|
||||
t-att-href="'/slides/slide/%s/set_completed?%s' % (slide.id, 'next_slide=%s' % next_slide.id if next_slide else '')"
|
||||
class="o_wslides_slide_button_done">Set Done</a>
|
||||
<t t-if="next_slide">
|
||||
<a class="o_wslides_slide_button" t-attf-href="/slides/slide/#{next_slide}">Next</a>
|
||||
<a class="o_wslides_slide_button" t-attf-href="/slides/slide/#{slug(next_slide)}">Next</a>
|
||||
</t>
|
||||
<a t-if="slide.channel_id.channel_type == 'training'" t-attf-href="/slides/slide/#{slug(slide)}?fullscreen=1" class="o_wslides_slide_button_fullscreen ml-2"><i class="fa fa-desktop mr-2"></i>fullscreen</a>
|
||||
</div>
|
||||
@@ -416,6 +417,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<template id="website_slides_forum_header" inherit_id="website_forum.header">
|
||||
<xpath expr="//div[hasclass('container')]" position="before">
|
||||
<t t-if="forum.slide_channel_id">
|
||||
<div class="row" style="background-color: #66445e; height: 48px;">
|
||||
<div class="container o_wprofile_navtabs_container">
|
||||
<ul class="nav nav-tabs o_wprofile_nav_tabs" role="tablist" id="profile_extra_info_tablist">
|
||||
<div class="row o_wforum_elearning_navtabs_container">
|
||||
<div class="container">
|
||||
<ul class="nav nav-tabs o_wprofile_nav_tabs mt-0 flex-nowrap" role="tablist" id="profile_extra_info_tablist">
|
||||
<li class="nav-item">
|
||||
<a t-att-href="'/slides/%s%s?%s' % (slug(forum.slide_channel_id), '/%s' % slug(category) if category else '', keep_query('debug'))"
|
||||
t-att-class="'nav-link o_wprofile_navlink'" style="border-left: 0px">
|
||||
|
||||
Reference in New Issue
Block a user