[MERGE][ADD] website{_profile,_slides,_forum,*} : new profile page yayy
Purpose of this merge is to provide a completely new and improved user profile
page linked to gamification, slides and forum. Those frontend applications
now heavily rely on website profile and add their own custom content like
channel or forum participation or challenges results. This merge is linked
to the currently under development eLearning feature [1][2].
This merge contains notably
* review website profile page centralized in a new module : website_profile;
* move everything that was linked to the profile from website_forum in
website_profile;
* properly add forum informations in website_forum through inheritance;
* add channel / course informations in website_slides through inheritance;
A new bridge module is added that is website_slide_forum. It makes a link
between those two applications. Publishers can create a forum linked to a
course allowing participants to communicate and ask questions related to a
course.
Profile page is contextualized. When coming from a channel informations about
this channel is displayed. Linked forum information is added. When coming from
a specific forum only information related to this forum is displayed.
Otherwise a profile page with aggregated information is displayed.
Some other improvements are done: ranks, karma, badges from gamification are
included in forum and eLearning, 'new badge notifications' mechanism is removed
as email are send, and various other improvements. See sub commits for more
details.
Merge linked to task ID 1922159.
[1] see task ID 1902304 (main eLearning task) PR #29876;
[2] see task ID 1936153 (new homepage for slides) PR #30770;
closes odoo/odoo#30988
This commit is contained in:
@@ -32,15 +32,6 @@
|
||||
<field name="image" type="base64" file="gamification/static/img/badge_idea-image.png"/>
|
||||
</record>
|
||||
|
||||
<!-- subtype for badge allocation -->
|
||||
<record id="mt_badge_granted" model="mail.message.subtype">
|
||||
<field name="name">Badge Granted</field>
|
||||
<field name="res_model">res.users</field>
|
||||
<field name="default" eval="True"/>
|
||||
<field name="hidden" eval="True"/>
|
||||
<field name="description">Badge Granted</field>
|
||||
</record>
|
||||
|
||||
<record id="email_template_badge_received" model="mail.template">
|
||||
<field name="name">Received Badge</field>
|
||||
<field name="subject">New badge ${object.badge_id.name} granted</field>
|
||||
|
||||
@@ -74,8 +74,10 @@ class Users(models.Model):
|
||||
old_rank = user.rank_id
|
||||
for i in range(0, len(ranks)):
|
||||
if user.karma >= ranks[i]['karma_min']:
|
||||
user.rank_id = ranks[i]['rank'].id
|
||||
user.next_rank_id = ranks[i - 1]['rank'].id if i > 0 else False
|
||||
user.write({
|
||||
'rank_id': ranks[i]['rank'].id,
|
||||
'next_rank_id': ranks[i - 1]['rank'].id if i > 0 else False
|
||||
})
|
||||
break
|
||||
if old_rank != user.rank_id:
|
||||
user._rank_changed()
|
||||
|
||||
@@ -67,5 +67,7 @@
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="gamification_karma_ranks_menu" parent="gamification_menu" action="gamification_karma_ranks_action" sequence="40"/>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -14,7 +14,6 @@ Ask questions, get answers, no distractions
|
||||
'depends': [
|
||||
'auth_signup',
|
||||
'website_mail',
|
||||
'website_partner',
|
||||
'website_profile',
|
||||
],
|
||||
'data': [
|
||||
@@ -22,6 +21,7 @@ Ask questions, get answers, no distractions
|
||||
'views/forum.xml',
|
||||
'views/res_users_views.xml',
|
||||
'views/website_forum.xml',
|
||||
'views/website_forum_profile.xml',
|
||||
'views/ir_qweb.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'data/badges_question.xml',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import base64
|
||||
import json
|
||||
import lxml
|
||||
import requests
|
||||
@@ -10,40 +9,30 @@ import werkzeug.wrappers
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from odoo import http, modules, SUPERUSER_ID, tools, _
|
||||
from odoo import http, tools, _
|
||||
from odoo.addons.http_routing.models.ir_http import slug
|
||||
from odoo.addons.website.models.ir_http import sitemap_qs2dom
|
||||
from odoo.addons.website_profile.controllers.main import WebsiteProfile
|
||||
from odoo.http import request
|
||||
|
||||
|
||||
class WebsiteForum(http.Controller):
|
||||
class WebsiteForum(WebsiteProfile):
|
||||
_post_per_page = 10
|
||||
_user_per_page = 30
|
||||
|
||||
def _get_notifications(self):
|
||||
badge_subtype = request.env.ref('gamification.mt_badge_granted')
|
||||
if badge_subtype:
|
||||
msg = request.env['mail.message'].search([('subtype_id', '=', badge_subtype.id), ('needaction', '=', True)])
|
||||
else:
|
||||
msg = list()
|
||||
return msg
|
||||
|
||||
def _prepare_forum_values(self, forum=None, **kwargs):
|
||||
values = {
|
||||
'user': request.env.user,
|
||||
'is_public_user': request.env.user.id == request.website.user_id.id,
|
||||
'notifications': self._get_notifications(),
|
||||
def _prepare_user_values(self, **kwargs):
|
||||
values = super(WebsiteForum, self)._prepare_user_values(**kwargs)
|
||||
values['forum_welcome_message'] = request.httprequest.cookies.get('forum_welcome_message', False)
|
||||
values.update({
|
||||
'header': kwargs.get('header', dict()),
|
||||
'searches': kwargs.get('searches', dict()),
|
||||
'forum_welcome_message': request.httprequest.cookies.get('forum_welcome_message', False),
|
||||
'validation_email_sent': request.session.get('validation_email_sent', False),
|
||||
'validation_email_done': request.session.get('validation_email_done', False),
|
||||
}
|
||||
if forum:
|
||||
values['forum'] = forum
|
||||
})
|
||||
if kwargs.get('forum'):
|
||||
values['forum'] = kwargs.get('forum')
|
||||
elif kwargs.get('forum_id'):
|
||||
values['forum'] = request.env['forum.forum'].browse(kwargs.pop('forum_id'))
|
||||
values.update(kwargs)
|
||||
return values
|
||||
|
||||
# User and validation
|
||||
@@ -165,7 +154,7 @@ class WebsiteForum(http.Controller):
|
||||
|
||||
question_ids = Post.search(domain, limit=self._post_per_page, offset=pager['offset'], order=sorting)
|
||||
|
||||
values = self._prepare_forum_values(forum=forum, searches=post, header={'ask_hide': not forum.active})
|
||||
values = self._prepare_user_values(forum=forum, searches=post, header={'ask_hide': not forum.active})
|
||||
values.update({
|
||||
'main_object': tag or forum,
|
||||
'question_ids': question_ids,
|
||||
@@ -180,7 +169,7 @@ class WebsiteForum(http.Controller):
|
||||
|
||||
@http.route(['''/forum/<model("forum.forum", "[('website_id', 'in', (False, current_website_id))]"):forum>/faq'''], type='http', auth="public", website=True)
|
||||
def forum_faq(self, forum, **post):
|
||||
values = self._prepare_forum_values(forum=forum, searches=dict(), header={'is_guidelines': True}, **post)
|
||||
values = self._prepare_user_values(forum=forum, searches=dict(), header={'is_guidelines': True}, **post)
|
||||
return request.render("website_forum.faq", values)
|
||||
|
||||
@http.route('/forum/get_tags', type='http', auth="public", methods=['GET'], website=True, sitemap=False)
|
||||
@@ -210,7 +199,7 @@ class WebsiteForum(http.Controller):
|
||||
order_by = 'posts_count DESC'
|
||||
tags = request.env['forum.tag'].search(domain, limit=None, order=order_by)
|
||||
# prepare values and render template
|
||||
values = self._prepare_forum_values(forum=forum, searches={'tags': True}, **post)
|
||||
values = self._prepare_user_values(forum=forum, searches={'tags': True}, **post)
|
||||
|
||||
values.update({
|
||||
'tags': tags,
|
||||
@@ -256,7 +245,7 @@ class WebsiteForum(http.Controller):
|
||||
redirect_url = "/forum/%s/question/%s" % (slug(forum), slug(question.parent_id))
|
||||
return werkzeug.utils.redirect(redirect_url, 301)
|
||||
filters = 'question'
|
||||
values = self._prepare_forum_values(forum=forum, searches=post)
|
||||
values = self._prepare_user_values(forum=forum, searches=post)
|
||||
values.update({
|
||||
'main_object': question,
|
||||
'question': question,
|
||||
@@ -284,7 +273,7 @@ class WebsiteForum(http.Controller):
|
||||
def question_ask_for_close(self, forum, question, **post):
|
||||
reasons = request.env['forum.post.reason'].search([('reason_type', '=', 'basic')])
|
||||
|
||||
values = self._prepare_forum_values(**post)
|
||||
values = self._prepare_user_values(**post)
|
||||
values.update({
|
||||
'question': question,
|
||||
'forum': forum,
|
||||
@@ -327,7 +316,7 @@ class WebsiteForum(http.Controller):
|
||||
user = request.env.user
|
||||
if not user.email or not tools.single_email_re.match(user.email):
|
||||
return werkzeug.utils.redirect("/forum/%s/user/%s/edit?email_required=1" % (slug(forum), request.session.uid))
|
||||
values = self._prepare_forum_values(forum=forum, searches={}, header={'ask_hide': True})
|
||||
values = self._prepare_user_values(forum=forum, searches={}, header={'ask_hide': True})
|
||||
return request.render("website_forum.new_question", values)
|
||||
|
||||
@http.route(['/forum/<model("forum.forum"):forum>/new',
|
||||
@@ -390,7 +379,7 @@ class WebsiteForum(http.Controller):
|
||||
def post_edit(self, forum, post, **kwargs):
|
||||
tags = [dict(id=tag.id, name=tag.name) for tag in post.tag_ids]
|
||||
tags = json.dumps(tags)
|
||||
values = self._prepare_forum_values(forum=forum)
|
||||
values = self._prepare_user_values(forum=forum)
|
||||
values.update({
|
||||
'tags': tags,
|
||||
'post': post,
|
||||
@@ -458,7 +447,7 @@ class WebsiteForum(http.Controller):
|
||||
domain = [('forum_id', '=', forum.id), ('state', '=', 'pending')]
|
||||
posts_to_validate_ids = Post.search(domain)
|
||||
|
||||
values = self._prepare_forum_values(forum=forum)
|
||||
values = self._prepare_user_values(forum=forum)
|
||||
values.update({
|
||||
'posts_ids': posts_to_validate_ids,
|
||||
'queue_type': 'validation',
|
||||
@@ -478,7 +467,7 @@ class WebsiteForum(http.Controller):
|
||||
domain += [('name', 'ilike', kwargs.get('spam_post'))]
|
||||
flagged_posts_ids = Post.search(domain, order='write_date DESC')
|
||||
|
||||
values = self._prepare_forum_values(forum=forum)
|
||||
values = self._prepare_user_values(forum=forum)
|
||||
values.update({
|
||||
'posts_ids': flagged_posts_ids,
|
||||
'queue_type': 'flagged',
|
||||
@@ -497,7 +486,7 @@ class WebsiteForum(http.Controller):
|
||||
domain = [('forum_id', '=', forum.id), ('state', '=', 'offensive'), ('active', '=', False)]
|
||||
offensive_posts_ids = Post.search(domain, order='write_date DESC')
|
||||
|
||||
values = self._prepare_forum_values(forum=forum)
|
||||
values = self._prepare_user_values(forum=forum)
|
||||
values.update({
|
||||
'posts_ids': offensive_posts_ids,
|
||||
'queue_type': 'offensive',
|
||||
@@ -530,7 +519,7 @@ class WebsiteForum(http.Controller):
|
||||
def post_ask_for_mark_as_offensive(self, forum, post, **kwargs):
|
||||
offensive_reasons = request.env['forum.post.reason'].search([('reason_type', '=', 'offensive')])
|
||||
|
||||
values = self._prepare_forum_values(forum=forum)
|
||||
values = self._prepare_user_values(forum=forum)
|
||||
values.update({
|
||||
'question': post,
|
||||
'forum': forum,
|
||||
@@ -551,7 +540,6 @@ class WebsiteForum(http.Controller):
|
||||
|
||||
# User
|
||||
# --------------------------------------------------
|
||||
|
||||
@http.route(['''/forum/<model("forum.forum", "[('website_id', 'in', (False, current_website_id))]"):forum>/users''',
|
||||
'''/forum/<model("forum.forum"):forum>/users/page/<int:page>'''],
|
||||
type='http', auth="public", website=True)
|
||||
@@ -568,11 +556,10 @@ class WebsiteForum(http.Controller):
|
||||
users = User.sudo().search(dom, limit=step, offset=pager['offset'], order='karma DESC')
|
||||
|
||||
searches['users'] = 'True'
|
||||
values = self._prepare_forum_values(forum=forum, searches=searches)
|
||||
values = self._prepare_user_values(forum=forum, searches=searches)
|
||||
values .update({
|
||||
'users': users,
|
||||
'main_object': forum,
|
||||
'notifications': self._get_notifications(),
|
||||
'pager': pager,
|
||||
})
|
||||
|
||||
@@ -586,77 +573,69 @@ class WebsiteForum(http.Controller):
|
||||
return werkzeug.utils.redirect("/forum/%s/user/%d" % (slug(forum), partner.user_ids[0].id))
|
||||
return werkzeug.utils.redirect("/forum/%s" % slug(forum))
|
||||
|
||||
@http.route(['/forum/user/<int:user_id>/avatar'], type='http', auth="public", website=True, sitemap=False)
|
||||
def user_avatar(self, user_id=0, **post):
|
||||
status, headers, content = request.env['ir.http'].sudo().binary_content(
|
||||
model='res.users', id=user_id, field='image_medium', default_mimetype='image/png')
|
||||
|
||||
if not content:
|
||||
img_path = modules.get_module_resource('web', 'static/src/img', 'placeholder.png')
|
||||
with open(img_path, 'rb') as f:
|
||||
image = f.read()
|
||||
content = base64.b64encode(image)
|
||||
if status == 304:
|
||||
return werkzeug.wrappers.Response(status=304)
|
||||
image_base64 = base64.b64decode(content)
|
||||
headers.append(('Content-Length', len(image_base64)))
|
||||
response = request.make_response(image_base64, headers)
|
||||
response.status = str(status)
|
||||
return response
|
||||
|
||||
# Profile
|
||||
# -----------------------------------
|
||||
@http.route(['/forum/<model("forum.forum"):forum>/user/<int:user_id>'], type='http', auth="public", website=True)
|
||||
def open_user(self, forum, user_id=0, **post):
|
||||
User = request.env['res.users']
|
||||
def view_user_forum_profile(self, forum, user_id, **post):
|
||||
return werkzeug.utils.redirect('/profile/user/' + str(user_id) + '?forum_id=' + str(forum.id))
|
||||
|
||||
def _prepare_user_profile_values(self, user, **post):
|
||||
values = super(WebsiteForum, self)._prepare_user_profile_values(user, **post)
|
||||
if not post.get('no_forum'):
|
||||
if post.get('forum'):
|
||||
forums = post['forum']
|
||||
elif post.get('forum_id'):
|
||||
forums = request.env['forum.forum'].browse(int(post['forum_id']))
|
||||
values.update({'edit_button_url_param': 'forum_id=' + str(post['forum_id'])})
|
||||
else:
|
||||
forums = request.env['forum.forum'].search([])
|
||||
|
||||
values.update(self._prepare_user_values(forum=forums[0] if len(forums) == 1 else True, **post))
|
||||
if forums:
|
||||
values.update(self._prepare_open_forum_user(user, forums))
|
||||
return values
|
||||
|
||||
def _prepare_open_forum_user(self, user, forums, **kwargs):
|
||||
Post = request.env['forum.post']
|
||||
Vote = request.env['forum.post.vote']
|
||||
Activity = request.env['mail.message']
|
||||
Followers = request.env['mail.followers']
|
||||
Data = request.env["ir.model.data"]
|
||||
|
||||
user = User.sudo().search([('id', '=', user_id)])
|
||||
current_user = request.env.user.sudo()
|
||||
|
||||
# Users with high karma can see users with karma <= 0 for
|
||||
# moderation purposes, IFF they have posted something (see below)
|
||||
if (not user or (user.karma < 1 and current_user.karma < forum.karma_unlink_all)):
|
||||
return werkzeug.utils.redirect("/forum/%s" % slug(forum))
|
||||
values = self._prepare_forum_values(forum=forum, **post)
|
||||
|
||||
# questions and answers by user
|
||||
user_question_ids = Post.search([
|
||||
('parent_id', '=', False),
|
||||
('forum_id', '=', forum.id), ('create_uid', '=', user.id)],
|
||||
('forum_id', 'in', forums.ids), ('create_uid', '=', user.id)],
|
||||
order='create_date desc')
|
||||
count_user_questions = len(user_question_ids)
|
||||
|
||||
if (user_id != request.session.uid and not
|
||||
(user.website_published or
|
||||
(count_user_questions and current_user.karma > forum.karma_unlink_all))):
|
||||
return request.render("website_forum.private_profile", values, status=404)
|
||||
min_karma_unlink = min(forums.mapped('karma_unlink_all'))
|
||||
|
||||
# limit length of visible posts by default for performance reasons, except for the high
|
||||
# karma users (not many of them, and they need it to properly moderate the forum)
|
||||
post_display_limit = None
|
||||
if current_user.karma < forum.karma_unlink_all:
|
||||
if request.env.user.karma < min_karma_unlink:
|
||||
post_display_limit = 20
|
||||
|
||||
user_questions = user_question_ids[:post_display_limit]
|
||||
user_answer_ids = Post.search([
|
||||
('parent_id', '!=', False),
|
||||
('forum_id', '=', forum.id), ('create_uid', '=', user.id)],
|
||||
('forum_id', 'in', forums.ids), ('create_uid', '=', user.id)],
|
||||
order='create_date desc')
|
||||
count_user_answers = len(user_answer_ids)
|
||||
user_answers = user_answer_ids[:post_display_limit]
|
||||
|
||||
# showing questions which user following
|
||||
post_ids = [follower.res_id for follower in Followers.sudo().search([('res_model', '=', 'forum.post'), ('partner_id', '=', user.partner_id.id)])]
|
||||
followed = Post.search([('id', 'in', post_ids), ('forum_id', '=', forum.id), ('parent_id', '=', False)])
|
||||
post_ids = [follower.res_id for follower in Followers.sudo().search(
|
||||
[('res_model', '=', 'forum.post'), ('partner_id', '=', user.partner_id.id)])]
|
||||
followed = Post.search([('id', 'in', post_ids), ('forum_id', 'in', forums.ids), ('parent_id', '=', False)])
|
||||
|
||||
# showing Favourite questions of user.
|
||||
favourite = Post.search([('favourite_ids', '=', user.id), ('forum_id', '=', forum.id), ('parent_id', '=', False)])
|
||||
favourite = Post.search(
|
||||
[('favourite_ids', '=', user.id), ('forum_id', 'in', forums.ids), ('parent_id', '=', False)])
|
||||
|
||||
# votes which given on users questions and answers.
|
||||
data = Vote.read_group([('forum_id', '=', forum.id), ('recipient_id', '=', user.id)], ["vote"], groupby=["vote"])
|
||||
data = Vote.read_group([('forum_id', 'in', forums.ids), ('recipient_id', '=', user.id)], ["vote"],
|
||||
groupby=["vote"])
|
||||
up_votes, down_votes = 0, 0
|
||||
for rec in data:
|
||||
if rec['vote'] == '1':
|
||||
@@ -669,8 +648,10 @@ class WebsiteForum(http.Controller):
|
||||
|
||||
# activity by user.
|
||||
model, comment = Data.get_object_reference('mail', 'mt_comment')
|
||||
activities = Activity.search([('res_id', 'in', (user_question_ids + user_answer_ids).ids), ('model', '=', 'forum.post'), ('subtype_id', '!=', comment)],
|
||||
order='date DESC', limit=100)
|
||||
activities = Activity.search(
|
||||
[('res_id', 'in', (user_question_ids + user_answer_ids).ids), ('model', '=', 'forum.post'),
|
||||
('subtype_id', '!=', comment)],
|
||||
order='date DESC', limit=100)
|
||||
|
||||
posts = {}
|
||||
for act in activities:
|
||||
@@ -680,15 +661,15 @@ class WebsiteForum(http.Controller):
|
||||
|
||||
# TDE CLEANME MASTER: couldn't it be rewritten using a 'menu' key instead of one key for each menu ?
|
||||
if user == request.env.user:
|
||||
post['my_profile'] = True
|
||||
kwargs['my_profile'] = True
|
||||
else:
|
||||
post['users'] = True
|
||||
kwargs['users'] = True
|
||||
|
||||
values.update({
|
||||
values = {
|
||||
'uid': request.env.user.id,
|
||||
'user': user,
|
||||
'main_object': user,
|
||||
'searches': post,
|
||||
'searches': kwargs,
|
||||
'questions': user_questions,
|
||||
'count_questions': count_user_questions,
|
||||
'answers': user_answers,
|
||||
@@ -700,55 +681,11 @@ class WebsiteForum(http.Controller):
|
||||
'activities': activities,
|
||||
'posts': posts,
|
||||
'vote_post': vote_ids,
|
||||
})
|
||||
return request.render("website_forum.user_detail_full", values)
|
||||
|
||||
@http.route('/forum/<model("forum.forum"):forum>/user/<model("res.users"):user>/edit', type='http', auth="user", website=True)
|
||||
def edit_profile(self, forum, user, **kwargs):
|
||||
countries = request.env['res.country'].search([])
|
||||
values = self._prepare_forum_values(forum=forum, searches=kwargs)
|
||||
values.update({
|
||||
'email_required': kwargs.get('email_required'),
|
||||
'countries': countries,
|
||||
'notifications': self._get_notifications(),
|
||||
})
|
||||
return request.render("website_forum.edit_profile", values)
|
||||
|
||||
@http.route('/forum/<model("forum.forum"):forum>/user/<model("res.users"):user>/save', type='http', auth="user", methods=['POST'], website=True)
|
||||
def save_edited_profile(self, forum, user, **kwargs):
|
||||
values = {
|
||||
'name': kwargs.get('name'),
|
||||
'website': kwargs.get('website'),
|
||||
'email': kwargs.get('email'),
|
||||
'city': kwargs.get('city'),
|
||||
'country_id': int(kwargs.get('country')) if kwargs.get('country') else False,
|
||||
'website_description': kwargs.get('description'),
|
||||
'is_profile_page': True,
|
||||
'badge_category': 'forum',
|
||||
}
|
||||
|
||||
if 'clear_image' in kwargs:
|
||||
values['image'] = False
|
||||
elif kwargs.get('ufile'):
|
||||
image = kwargs.get('ufile').read()
|
||||
values['image'] = base64.b64encode(image)
|
||||
|
||||
if request.uid == user.id: # the controller allows to edit only its own privacy settings; use partner management for other cases
|
||||
values['website_published'] = kwargs.get('website_published') == 'True'
|
||||
user.write(values)
|
||||
return werkzeug.utils.redirect("/forum/%s/user/%d" % (slug(forum), user.id))
|
||||
|
||||
# Badges
|
||||
# --------------------------------------------------
|
||||
|
||||
@http.route('''/forum/<model("forum.forum", "[('website_id', 'in', (False, current_website_id))]"):forum>/badge''', type='http', auth="public", website=True)
|
||||
def badges(self, forum, **searches):
|
||||
Badge = request.env['gamification.badge']
|
||||
badges = Badge.sudo().search([('challenge_ids.category', '=', 'forum')])
|
||||
badges = sorted(badges, key=lambda b: b.stat_count_distinct, reverse=True)
|
||||
values = self._prepare_forum_values(forum=forum, searches={'badges': True})
|
||||
values.update({
|
||||
'badges': badges,
|
||||
})
|
||||
return request.render("website_forum.badge", values)
|
||||
return values
|
||||
|
||||
# Messaging
|
||||
# --------------------------------------------------
|
||||
|
||||
@@ -397,7 +397,7 @@ class Post(models.Model):
|
||||
res = super(Post, self)._default_website_meta()
|
||||
res['default_opengraph']['og:title'] = res['default_twitter']['twitter:title'] = self.name
|
||||
res['default_opengraph']['og:description'] = res['default_twitter']['twitter:description'] = self.plain_content
|
||||
res['default_opengraph']['og:image'] = res['default_twitter']['twitter:image'] = "/forum/user/%s/avatar" % (self.create_uid.id)
|
||||
res['default_opengraph']['og:image'] = res['default_twitter']['twitter:image'] = "/web/image/res.users/%s/image" % (self.create_uid.id)
|
||||
res['default_twitter']['twitter:card'] = 'summary'
|
||||
return res
|
||||
|
||||
|
||||
@@ -86,3 +86,11 @@ class Users(models.Model):
|
||||
@api.multi
|
||||
def open_website_url(self):
|
||||
return self.mapped('partner_id').open_website_url()
|
||||
|
||||
def get_gamification_redirection_data(self):
|
||||
res = super(Users, self).get_gamification_redirection_data()
|
||||
res.append({
|
||||
'url': '/forum',
|
||||
'label': 'See our Forum'
|
||||
})
|
||||
return res
|
||||
|
||||
@@ -68,28 +68,6 @@ $bronze: #eea91e;
|
||||
max-width: 60%;
|
||||
}
|
||||
|
||||
.badge-gold {
|
||||
color: $gold;
|
||||
}
|
||||
|
||||
.bg-gold {
|
||||
background-color: $gold;
|
||||
}
|
||||
|
||||
.badge-silver {
|
||||
color: $silver;
|
||||
}
|
||||
.bg-silver {
|
||||
background-color: $silver;
|
||||
}
|
||||
|
||||
.badge-bronze {
|
||||
color: $bronze;
|
||||
}
|
||||
.bg-bronze {
|
||||
background-color: $bronze;
|
||||
}
|
||||
|
||||
.oe_answer_true {
|
||||
color: theme-color('success');
|
||||
&:hover {
|
||||
@@ -191,9 +169,6 @@ $bronze: #eea91e;
|
||||
border-left: 3px solid;
|
||||
}
|
||||
|
||||
.o_wforum_people_photo_container {
|
||||
width: 33%;
|
||||
}
|
||||
.o_profile_main {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -46,8 +46,106 @@
|
||||
<div t-field="forum.welcome_message"/>
|
||||
</div>
|
||||
|
||||
<div class="container mt16 mb32 o_wforum_navbar_container">
|
||||
<div class="navbar navbar-expand-md navbar-light">
|
||||
<div class="container mt16 mb32 ">
|
||||
<t t-call="website_forum.forum_nav_header"></t>
|
||||
</div>
|
||||
|
||||
<div id="wrap" t-attf-class="container #{website_forum_action}">
|
||||
<div class="row">
|
||||
<div class="col o_left_forum_container mb16 mt16">
|
||||
<div t-if="not validation_email_sent and not is_public_user and user.karma == 0" class="alert alert-danger alert-dismissable" role="alert">
|
||||
<button type="button" class="close validation_email_close" data-dismiss="alert" aria-label="Close">&times;</button>
|
||||
<div>
|
||||
<p>
|
||||
It appears your email has not been verified.
|
||||
<a class="send_validation_email" href="#" t-att-forum-id="forum.id">Click here to send a verification email allowing you to participate to the forum.</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="validation_email_done" class="alert alert-success alert-dismissable" role="status">
|
||||
<button type="button" class="close validated_email_close" data-dismiss="alert" aria-label="Close">&times;</button>
|
||||
<div>
|
||||
<p>Congratulations! Your email has just been validated. You may now participate to our forums.</p>
|
||||
</div>
|
||||
</div>
|
||||
<t t-raw="0"/>
|
||||
</div>
|
||||
<t t-if="not is_profile_page">
|
||||
<div class="col-lg-3" id="right-column">
|
||||
<a role="button" type="button" class="btn btn-primary btn-lg btn-block mb-3" t-attf-href="/forum/#{slug(forum)}/ask">Ask a Question</a>
|
||||
<div class="card bg-secondary" t-if="question">
|
||||
<h6 class="card-header"><b>Writer</b></h6>
|
||||
<div class="card-body o_card_people">
|
||||
<img class="o_wforum_people_photo_container float-left mr-3" t-attf-src="/web/image/res.users/#{question.create_uid.id}/image_medium"/>
|
||||
<b><i class="fa fa-diamond text-secondary"/> <t t-esc="question.create_uid.sudo().karma"/></b>
|
||||
<h6>
|
||||
<b><a t-attf-href="/forum/#{slug(forum)}/user/#{question.create_uid.sudo().id}" t-field="question.create_uid" t-options='{"widget": "contact", "fields": ["name", "country_id"]}'/></b>
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card my-3 bg-secondary" t-if="question and not len(question.tag_ids)<=0">
|
||||
<h6 class="card-header">
|
||||
<b>
|
||||
<t t-if="len(question.tag_ids)<=1">
|
||||
Tag
|
||||
</t>
|
||||
<t t-else="">
|
||||
Tags
|
||||
</t>
|
||||
</b>
|
||||
</h6>
|
||||
<div class="card-body">
|
||||
<t t-foreach="question.tag_ids" t-as="tag">
|
||||
<a t-attf-href="/forum/#{ slug(forum) }/tag/#{ tag.id }/questions" class="badge badge-primary" t-field="tag.name"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card my-3 bg-secondary" t-if="question">
|
||||
<h6 class="card-header"><b>Keep Informed</b></h6>
|
||||
<div class="card-body o_subscribe_post">
|
||||
<t t-call="website_mail.follow">
|
||||
<t t-set="object" t-value="question"/>
|
||||
<t t-set="div_class" t-value="'mx-auto'"/>
|
||||
</t>
|
||||
<div class="text-center mt-3">
|
||||
<strong><t t-raw="len(question.message_follower_ids)"/> follower(s)</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card my-3 bg-secondary" id="about_forum">
|
||||
<h6 class="card-header"><b>About This Community</b></h6>
|
||||
<div class="card-body">
|
||||
<span t-field="forum.description"/>
|
||||
<a t-if="not header.get('is_guidelines')" t-attf-href="/forum/#{slug(forum)}/faq" class="btn btn-primary mt-3">Read Guidelines</a>
|
||||
<a t-if="header.get('is_guidelines')" t-attf-href="/forum/#{slug(forum)}" class="btn btn-primary mt-3"> Back to <span t-field="forum.name"/></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card my-3 bg-secondary" id="moderation_tools" t-if="user.karma>=forum.karma_moderate">
|
||||
<h6 class="card-header"><b>Moderation Tools</b></h6>
|
||||
<div class="card-body">
|
||||
<a t-attf-href="/forum/#{slug(forum)}/validation_queue" class="text-dark"><i class="fa fa-check-square-o mr-2 text-primary"/><strong><t t-esc="forum.count_posts_waiting_validation"/></strong> waiting for validation</a>
|
||||
<hr style="margin-top:4px;margin-bottom:0;padding-bottom:4px;" />
|
||||
<a t-attf-href="/forum/#{slug(forum)}/flagged_queue" class="text-dark"><i class="fa fa-flag mr-2 text-primary"/><strong id="count_flagged_posts"><t t-esc="forum.count_flagged_posts"/></strong> post<t t-if="forum.count_flagged_posts>1">s</t> flagged</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_structure" id="oe_structure_website_forum_header_1"/>
|
||||
</t>
|
||||
<t t-else="" t-call="website_forum.layout">
|
||||
<t t-set="head">
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
</t>
|
||||
<div class="text-center text-muted">
|
||||
<p class="css_editable_hidden"><h2>This forum has been archived.</h2></p>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="forum_nav_header">
|
||||
<div class="navbar navbar-expand-md navbar-light">
|
||||
<a class="navbar-brand" t-attf-href="/forum/#{slug(forum)}">
|
||||
<span t-field="forum.name"/>
|
||||
</a>
|
||||
@@ -73,7 +171,7 @@
|
||||
t-attf-class="nav-link #{searches.get('tags') and 'active' or ''}">Tags</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a t-attf-href="/forum/#{ slug(forum) }/badge"
|
||||
<a t-attf-href="/profile/badge?category=forum"
|
||||
t-attf-class="nav-link #{searches.get('badges') and 'active' or ''}">Badges</a>
|
||||
</li>
|
||||
<li class="nav-item" t-if="user.karma >= forum.karma_moderate">
|
||||
@@ -83,7 +181,7 @@
|
||||
</ul>
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li t-if="not is_public_user" class="nav-item">
|
||||
<a t-attf-href="/forum/#{ slug(forum) }/user/#{uid}" t-attf-class="text-dark font-weight-bold nav-link#{searches.get('my_profile') and ' active' or ''}">
|
||||
<a t-attf-href="/profile/user/#{uid}?forum_id=#{forum.id}" t-attf-class="text-dark font-weight-bold nav-link#{searches.get('my_profile') and ' active' or ''}">
|
||||
<i class="fa fa-diamond text-secondary" /> <t t-esc="user_id.karma"/>
|
||||
</a>
|
||||
</li>
|
||||
@@ -102,103 +200,6 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="wrap" t-attf-class="container #{website_forum_action}">
|
||||
<div class="row">
|
||||
<div class="col-lg-9 o_left_forum_container mb16">
|
||||
<div t-foreach="notifications or []" t-as="notification" class="alert alert-success alert-dismissable" role="status">
|
||||
<button type="button" class="close notification_close" t-att-id="notification.id" data-dismiss="alert" aria-label="Close">&times;</button>
|
||||
<div t-field="notification.body"/>
|
||||
<a t-attf-href="/forum/#{ slug(forum) }/user/#{ user.id }#badges" class="fa fa-arrow-right">View Your Badges</a>
|
||||
</div>
|
||||
<div t-if="not validation_email_sent and not is_public_user and user.karma == 0" class="alert alert-danger alert-dismissable" role="alert">
|
||||
<button type="button" class="close validation_email_close" data-dismiss="alert" aria-label="Close">&times;</button>
|
||||
<div>
|
||||
<p>
|
||||
It appears your email has not been verified.
|
||||
<a class="send_validation_email" href="#" t-att-forum-id="forum.id">Click here to send a verification email allowing you to participate to the forum.</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="validation_email_done" class="alert alert-success alert-dismissable" role="status">
|
||||
<button type="button" class="close validated_email_close" data-dismiss="alert" aria-label="Close">&times;</button>
|
||||
<div>
|
||||
<p>Congratulations! Your email has just been validated. You may now participate to our forums.</p>
|
||||
</div>
|
||||
</div>
|
||||
<t t-raw="0"/>
|
||||
</div>
|
||||
<div class="col-lg-3" id="right-column">
|
||||
<a role="button" type="button" class="btn btn-primary btn-lg btn-block mb-3" t-attf-href="/forum/#{slug(forum)}/ask">Ask a Question</a>
|
||||
<div class="card bg-secondary" t-if="question">
|
||||
<h6 class="card-header"><b>Writer</b></h6>
|
||||
<div class="card-body o_card_people">
|
||||
<img class="o_wforum_people_photo_container float-left mr-3" t-attf-src="/forum/user/#{question.create_uid.id}/avatar"/>
|
||||
<b><i class="fa fa-diamond text-secondary"/> <t t-esc="question.create_uid.sudo().karma"/></b>
|
||||
<h6>
|
||||
<b><a t-attf-href="/forum/#{slug(forum)}/user/#{question.create_uid.sudo().id}" t-field="question.create_uid" t-options='{"widget": "contact", "fields": ["name", "country_id"]}'/></b>
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card my-3 bg-secondary" t-if="question and not len(question.tag_ids)<=0">
|
||||
<h6 class="card-header">
|
||||
<b>
|
||||
<t t-if="len(question.tag_ids)<=1">
|
||||
Tag
|
||||
</t>
|
||||
<t t-else="">
|
||||
Tags
|
||||
</t>
|
||||
</b>
|
||||
</h6>
|
||||
<div class="card-body">
|
||||
<t t-foreach="question.tag_ids" t-as="tag">
|
||||
<a t-attf-href="/forum/#{ slug(forum) }/tag/#{ tag.id }/questions" class="badge badge-primary" t-field="tag.name"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card my-3 bg-secondary" t-if="question">
|
||||
<h6 class="card-header"><b>Keep Informed</b></h6>
|
||||
<div class="card-body o_subscribe_post">
|
||||
<t t-call="website_mail.follow">
|
||||
<t t-set="object" t-value="question"/>
|
||||
<t t-set="div_class" t-value="'mx-auto'"/>
|
||||
</t>
|
||||
<div class="text-center mt-3">
|
||||
<strong><t t-raw="len(question.message_follower_ids)"/> follower(s)</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card my-3 bg-secondary" id="about_forum">
|
||||
<h6 class="card-header"><b>About This Community</b></h6>
|
||||
<div class="card-body">
|
||||
<span t-field="forum.description"/>
|
||||
<a t-if="not header.get('is_guidelines')" t-attf-href="/forum/#{slug(forum)}/faq" class="btn btn-primary mt-3">Read Guidelines</a>
|
||||
<a t-if="header.get('is_guidelines')" t-attf-href="/forum/#{slug(forum)}" class="btn btn-primary mt-3"> Back to <span t-field="forum.name"/></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card my-3 bg-secondary" id="moderation_tools" t-if="user.karma>=forum.karma_moderate">
|
||||
<h6 class="card-header"><b>Moderation Tools</b></h6>
|
||||
<div class="card-body">
|
||||
<a t-attf-href="/forum/#{slug(forum)}/validation_queue" class="text-dark"><i class="fa fa-check-square-o mr-2 text-primary"/><strong><t t-esc="forum.count_posts_waiting_validation"/></strong> waiting for validation</a>
|
||||
<hr style="margin-top:4px;margin-bottom:0;padding-bottom:4px;" />
|
||||
<a t-attf-href="/forum/#{slug(forum)}/flagged_queue" class="text-dark"><i class="fa fa-flag mr-2 text-primary"/><strong id="count_flagged_posts"><t t-esc="forum.count_flagged_posts"/></strong> post<t t-if="forum.count_flagged_posts>1">s</t> flagged</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_structure" id="oe_structure_website_forum_header_1"/>
|
||||
</t>
|
||||
<t t-else="" t-call="website_forum.layout">
|
||||
<t t-set="head">
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
</t>
|
||||
<div class="text-center text-muted">
|
||||
<p class="css_editable_hidden"><h2>This forum has been archived.</h2></p>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Template use to edit the welcome_message -->
|
||||
@@ -218,14 +219,14 @@
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<t t-foreach="question.tag_ids" t-as="question_tag">
|
||||
<a t-attf-href="/forum/#{ slug(forum) }/tag/#{slug(question_tag)}/questions">
|
||||
<a t-attf-href="/forum/#{ slug(question_tag.forum_id) }/tag/#{slug(question_tag)}/questions">
|
||||
<span t-attf-class="float-right mb-1 mr-1 badge #{tag and tag.name == question_tag.name and 'badge-primary' or 'badge-secondary'}" t-field="question_tag.name"/>
|
||||
</a>
|
||||
</t>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="question-name">
|
||||
<a t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(question) }">
|
||||
<a t-attf-href="/forum/#{ slug(question.forum_id) }/question/#{ slug(question) }">
|
||||
<h4 t-field="question.name" class="d-inline"/>
|
||||
</a>
|
||||
<span t-if="not question.active and question.state!='offensive'"><b> [Deleted]</b></span>
|
||||
@@ -240,12 +241,12 @@
|
||||
<span class="ml-4 mr-1 fa fa-calendar"></span><span t-field="question.write_date" t-options='{"format":"short"}'/>
|
||||
<span class="ml-4 mr-1 fa fa-eye"></span><span t-field="question.views" /> <t t-if="question.views<=1">View</t><t t-else="">Views</t>
|
||||
<i class="ml-4 fa fa-comments text-primary"/>
|
||||
<a t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(question) }">
|
||||
<a t-attf-href="/forum/#{ slug(question.forum_id) }/question/#{ slug(question) }">
|
||||
<t t-esc="question.child_count"/>
|
||||
<t t-if="question.child_count<=1">Answer</t>
|
||||
<t t-else="">Answers</t>
|
||||
</a>
|
||||
<a t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(question) }">
|
||||
<a t-attf-href="/forum/#{ slug(question.forum_id) }/question/#{ slug(question) }">
|
||||
<span class="ml-4 mr-1 fa fa-star"></span><span t-field="question.favourite_count"/> <t t-if="question.views<=1">Favorite</t><t t-else="">Favorites</t>
|
||||
</a>
|
||||
<t t-if="question.state == 'flagged'" class="text-black">
|
||||
@@ -282,7 +283,7 @@
|
||||
<div t-attf-class="#{len(answer.vote_ids) and 'oe_green' or 'oe_grey'}" t-esc="len(answer.vote_ids)"/>
|
||||
</div>
|
||||
<div class="question-name col">
|
||||
<a style="font-size: 15px;" t-attf-href="/forum/#{ slug(forum) }/question/#{ answer.parent_id.id }/#answer-#{ answer.id }" t-esc="answer.parent_id.name"/>
|
||||
<a style="font-size: 15px;" t-attf-href="/forum/#{ slug(answer.forum_id) }/question/#{ answer.parent_id.id }/#answer-#{ answer.id }" t-esc="answer.parent_id.name"/>
|
||||
<t t-if="len(answer.website_message_ids)>0">
|
||||
(<t t-esc="len(answer.website_message_ids)"/>
|
||||
<t t-if="len(answer.website_message_ids)>1"> Comments</t>
|
||||
@@ -308,7 +309,7 @@
|
||||
<template id="moderation_display_post_answer">
|
||||
<div class="clearfix">
|
||||
<div class="question-name">
|
||||
<a style="font-size: 15px;" t-attf-href="/forum/#{ slug(forum) }/question/#{ answer.parent_id.id }/#answer-#{ answer.id }" t-esc="answer.parent_id.name"/>
|
||||
<a style="font-size: 15px;" t-attf-href="/forum/#{ slug(answer.forum_id) }/question/#{ answer.parent_id.id }/#answer-#{ answer.id }" t-esc="answer.parent_id.name"/>
|
||||
<b>[Answer]</b>
|
||||
<span t-if="not answer.active and answer.state=='offensive'"><b> [Offensive]</b></span>
|
||||
<span t-if="not answer.active and answer.state=='offensive' and answer.closed_reason_id"><b> [<t t-esc="answer.closed_reason_id.name[0].upper() + answer.closed_reason_id.name[1:]"/>]</b></span>
|
||||
@@ -872,7 +873,7 @@
|
||||
<t t-call="website_forum.forum_user_tooltip"/>
|
||||
</div>
|
||||
<a t-att-href="user_profile_url" class="o_forum_user_info">
|
||||
<img class="float-left o_forum_avatar" t-attf-src="/forum/user/#{answer.create_uid.id}/avatar" alt="Avatar"/>
|
||||
<img class="float-left o_forum_avatar" t-attf-src="/web/image/res.users/#{answer.create_uid.id}/image_medium" alt="Avatar"/>
|
||||
</a>
|
||||
<a t-att-href="user_profile_url"
|
||||
t-field="answer.create_uid"
|
||||
@@ -928,7 +929,7 @@
|
||||
<div class="o_forum_user_bio text-left card">
|
||||
<div class="card-body">
|
||||
<a t-att-href="user_profile_url">
|
||||
<img class="float-left img rounded o_forum_avatar_big" t-attf-src="/forum/user/#{answer.create_uid.id}/avatar" alt="Avatar"/>
|
||||
<img class="float-left img rounded o_forum_avatar_big" t-attf-src="/web/image/res.users/#{answer.create_uid.id}/image_medium" alt="Avatar"/>
|
||||
</a>
|
||||
<div class="o_forum_tooltip_header">
|
||||
<h4 class="o_forum_tooltip_heading"><a t-att-href="user_profile_url"
|
||||
@@ -1037,35 +1038,6 @@
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="badge" name="Forum Badges">
|
||||
<t t-call="website_forum.header">
|
||||
<h1 class="o_page_header">
|
||||
Badges
|
||||
</h1>
|
||||
<p>
|
||||
Besides gaining reputation with your questions and answers,
|
||||
you receive badges for being especially helpful. Badges
|
||||
appear on your profile page, and your posts.
|
||||
</p>
|
||||
<table class="table mt32 mb64">
|
||||
<tr t-foreach="badges" t-as="badge">
|
||||
<td>
|
||||
<span class="badge badge-pill float-left">
|
||||
<span t-if="badge.level == 'gold'"><i class="fa fa-trophy badge-gold"/> <b t-field="badge.name" class="text-white bg-gold badge-pill"/></span>
|
||||
<span t-if="badge.level == 'silver'"><i class="fa fa-trophy badge-silver"/> <b t-field="badge.name" class="text-white bg-silver badge-pill"/></span>
|
||||
<span t-if="badge.level == 'bronze'"><i class="fa fa-trophy badge-bronze"/> <b t-field="badge.name" class="text-white bg-bronze badge-pill"/></span>
|
||||
</span>
|
||||
</td><td>
|
||||
<b t-esc="badge.stat_count_distinct"/>
|
||||
<i class="text-muted"> awarded users</i>
|
||||
</td><td>
|
||||
<span t-field="badge.description"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="moderation_queue" name="Forum Moderation Queue">
|
||||
<t t-call="website_forum.header">
|
||||
<t t-set="website_forum_action" t-value="'o_wforum_moderation_queue'"/>
|
||||
@@ -1114,7 +1086,7 @@
|
||||
<input type="checkbox" t-att-value="user.id" class="custom-control-input" t-attf-id="user_#{user.id}"/>
|
||||
<label class="custom-control-label" t-attf-for="user_#{user.id}">
|
||||
<div class="col-11 d-inline-flex">
|
||||
<img class="d-inline img o_forum_avatar" t-attf-src="/forum/user/#{user.id}/avatar" alt="Avatar"/>
|
||||
<img class="d-inline img o_forum_avatar" t-attf-src="/web/image/res.users/#{user.id}/image_medium" alt="Avatar"/>
|
||||
<b t-esc="user.name" class="d-inline"></b>
|
||||
</div>
|
||||
</label>
|
||||
@@ -1204,9 +1176,9 @@
|
||||
<div t-foreach="users" t-as="u" class="col-xl-4 col-md-6 col-sm-12 mb-4">
|
||||
<div class="card o_card_people h-100">
|
||||
<div class="card-body">
|
||||
<img class="o_wforum_people_photo_container float-left mr-3" t-attf-src="/forum/user/#{u.id}/avatar"/>
|
||||
<img class="o_wforum_people_photo_container float-left mr-3" t-attf-src="/web/image/res.users/#{u.id}/image_medium"/>
|
||||
<b><i class="text-secondary mr-1 fa fa-diamond"/><t t-esc="u.karma"/></b>
|
||||
<h4 class="font-weight-bold o_card_people_name"><a t-attf-href="/forum/#{slug(forum)}/user/#{u.id}" t-field="u.name"/></h4>
|
||||
<h4 class="font-weight-bold o_card_people_name"><a t-attf-href="/profile/user/#{u.id}?forum_id=#{forum.id}" t-field="u.name"/></h4>
|
||||
<t t-if="u.country_id">
|
||||
<span t-field="u.country_id.image" t-options='{"widget": "image", "class": "country_flag_people"}'/>
|
||||
</t>
|
||||
@@ -1233,330 +1205,6 @@
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!--Private profile-->
|
||||
<template id="private_profile">
|
||||
<t t-call="website_forum.header">
|
||||
<t t-set="head">
|
||||
<t t-raw="head"/>
|
||||
<meta name="robots" content="noindex follow"/>
|
||||
</t>
|
||||
<h1 class="mt32">This profile is private!</h1>
|
||||
<p>
|
||||
<a t-attf-href="/forum/#{slug(forum)}">Return to the question list.</a>
|
||||
</p>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="edit_profile" name="Forum Edit Profile">
|
||||
<t t-call="website_forum.header">
|
||||
<h1 class="o_page_header">Edit Profile</h1>
|
||||
<div>
|
||||
<form t-attf-action="/forum/#{slug(forum)}/user/#{slug(user)}/save" method="post" role="form" class="js_website_submit_form row" enctype="multipart/form-data">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="file" class="d-none o_forum_file_upload" name="ufile" accept="image/*"/>
|
||||
<div class="col-3">
|
||||
<div class="card o_card_people">
|
||||
<div class="card-body">
|
||||
<img class="w-100 mb-3" t-attf-src="/forum/user/#{user.id}/avatar"/>
|
||||
<div class="text-center">
|
||||
<a href="#" class="o_forum_profile_pic_edit btn btn-primary" aria-label="Edit">
|
||||
<i class="fa fa-pencil fa-1g float-sm-none float-md-left" title="Edit"></i>
|
||||
</a>
|
||||
<a href="#" title="Clear" aria-label="Clear" class="btn border-primary">
|
||||
<i class="fa fa-trash-o float-sm-none float-md-right o_forum_profile_pic_clear"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="form-group mt-3 mb-0 pt-2 border-top">
|
||||
<label class="text-primary" for="user_website_published" t-if="user.id == uid"><b>Public profile</b></label>
|
||||
<div class=" mb-0 float-right" t-if="user.id == uid">
|
||||
<input type="checkbox" class="mt8" name="website_published" id="user_website_published" value="True" t-if="not user.website_published"/>
|
||||
<input type="checkbox" class="mt8" name="website_published" id="user_website_published" value="True" checked="checked" t-if="user.website_published"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<input name="user_id" t-att-value="user.id" type="hidden"/>
|
||||
<div class="form-group col-12">
|
||||
<label class="text-primary mb-1 d-block" for="user_name"><b>Real name</b></label>
|
||||
<div>
|
||||
<input type="text" class="form-control" name="name" id="user_name" required="True" t-attf-value="#{user.name}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-6">
|
||||
<label class="mb-1 text-primary" for="user_website"><b>Website</b></label>
|
||||
<div>
|
||||
<input type="text" class="form-control" name="website" id="user_website" t-attf-value="#{user.partner_id.website or ''}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-6">
|
||||
<div t-if="email_required" class="alert alert-danger alert-dismissable oe_forum_email_required" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert">x</button>
|
||||
<p>Please enter a valid email address in order to receive notifications from answers or comments.</p>
|
||||
</div>
|
||||
<label class="mb-1 text-primary" for="user_email"><b>Email</b></label>
|
||||
<div>
|
||||
<input type="text" class="form-control" name="email" id="user_email" required="True" t-attf-value="#{user.partner_id.email}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-6">
|
||||
<label class="mb-1 text-primary" for="user_city"><b>City</b></label>
|
||||
<div>
|
||||
<input type="text" class="form-control" name="city" id="user_city" t-attf-value="#{user.partner_id.city or ''}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-6">
|
||||
<label class="mb-1 text-primary"><b>Country</b></label>
|
||||
<div>
|
||||
<select class="form-control" name="country">
|
||||
<option value="">Country...</option>
|
||||
<t t-foreach="countries or []" t-as="country">
|
||||
<option t-att-value="country.id" t-att-selected="country.id == user.partner_id.country_id.id"><t t-esc="country.name"/></option>
|
||||
</t>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!--Note: using website_description fiels instead of using commnt firld of partner-->
|
||||
<div class="form-group col-12">
|
||||
<label class="mb-1 text-primary" for="description"><b>Biography</b></label>
|
||||
<textarea name="description" id="description" style="min-height: 120px" required="True"
|
||||
class="form-control load_editor"><t t-esc="user.partner_id.website_description"/></textarea>
|
||||
</div>
|
||||
<div class="col">
|
||||
<button type="submit" class="btn btn-primary btn">Update</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="user_detail_full">
|
||||
<t t-call="website_forum.header">
|
||||
<a class="float-right mt-3"
|
||||
t-if="request.env.user == user"
|
||||
t-attf-href="/forum/#{slug(forum)}/user/#{slug(user)}/edit">Edit<i class="fa fa-angle-right ml-2"/>
|
||||
</a>
|
||||
<h1 class="o_page_header">Profile</h1>
|
||||
<div class="o_profile_main row">
|
||||
<div class="col-md-4">
|
||||
<div class="card o_card_people">
|
||||
<div class="card-header">
|
||||
<img class="o_wforum_people_photo_container float-left mr-3" t-attf-src="/forum/user/#{user.id}/avatar"/>
|
||||
<h3 class="text-primary o_card_people_name"><b t-field="user.name"/></h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<b class="text-muted">Karma</b>
|
||||
<span class="float-right"><i class="fa fa-diamond text-secondary mr-2"/><b t-field="user.karma"/></span>
|
||||
<div class="border-top mt-2 pt-2">
|
||||
<b class="text-muted">Badges</b>
|
||||
<span class="float-right">
|
||||
<i class="fa fa-trophy badge-gold" role="img" aria-label="Gold badge" title="Gold badge"/>
|
||||
<b t-esc="user.gold_badge" class=""/>
|
||||
<i class="fa fa-trophy badge-silver ml16" role="img" aria-label="Silver badge" title="Silver badge"/>
|
||||
<b t-esc="user.silver_badge" class="" />
|
||||
<i class="fa fa-trophy badge-bronze ml16" role="img" aria-label="Bronze badge" title="Bronze badge"/>
|
||||
<b t-esc="user.bronze_badge" class="" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="border-top mt-2 pt-2">
|
||||
<b class="text-muted">Votes</b>
|
||||
<span class="float-right">
|
||||
<i class="fa fa-thumbs-up text-success" role="img" aria-label="Positive votes" title="Positive votes"/>
|
||||
<b t-esc="up_votes"/>
|
||||
<i class="fa fa-thumbs-down text-danger ml-3" role="img" aria-label="Negative votes" title="Negative votes"/>
|
||||
<b t-esc="down_votes"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<b class="text-muted">Contribution</b>
|
||||
<div class="card mt-1 mb-4">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<b class="text-primary">Member since</b>
|
||||
</div>
|
||||
<div class="col">
|
||||
<span t-field="user.create_date" t-options='{"format": "short"}'/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-top pt-2 mt-2 ">
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<b class="text-primary">Last connection</b>
|
||||
</div>
|
||||
<div class="col">
|
||||
<span t-field="user.login_date" t-options='{"format": "short"}'/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<b class="text-muted">Bio</b>
|
||||
<div class="card mt-1 mb-4">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<b class="text-primary">Location</b>
|
||||
</div>
|
||||
<div class="col">
|
||||
<t t-if="user.country_id"> <span t-field="user.country_id.image" t-options='{"widget": "image", "class": "country_flag"}' class="mr-2"/></t>
|
||||
<span t-field="user.city"/>
|
||||
<span t-if="user.city and user.country_id">, </span>
|
||||
<span t-field="user.country_id"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-top pt-2 mt-2">
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<b class="text-primary">Website</b>
|
||||
</div>
|
||||
<div class="col">
|
||||
<a t-att-href="user.website" t-if="user.website">
|
||||
<span t-field="user.website"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<b class="text-muted">Introduction</b>
|
||||
<div class="card mt-1 mb-4">
|
||||
<div class="card-body">
|
||||
<span t-field="user.partner_id.website_description" class="oe_no_empty"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a role="tab" aria-controls="questions" href="#questions" class="nav-link active" data-toggle="tab"><t t-esc="count_questions"/> Questions</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a role="tab" aria-controls="answers" href="#answers" class="nav-link" data-toggle="tab"><t t-esc="count_answers"/> Answers</a>
|
||||
</li>
|
||||
<li t-if="uid == user.id" class="nav-item">
|
||||
<a role="tab" aria-controls="favourite_question" href="#favourite_question" class="nav-link" data-toggle="tab"><t t-esc="len(favourite)"/> Favourite Questions</a>
|
||||
</li>
|
||||
<li t-if="uid == user.id" class="nav-item">
|
||||
<a role="tab" aria-controls="followed_question" href="#followed_question" class="nav-link" data-toggle="tab"><t t-esc="len(followed)"/> Followed Questions</a>
|
||||
</li>
|
||||
<li t-if="uid == user.id" class="nav-item">
|
||||
<a role="tab" aria-controls="activity" href="#activity" class="nav-link" data-toggle="tab">Activity</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a role="tab" aria-controls="badges" href="#badges" class="nav-link" data-toggle="tab">Badges</a>
|
||||
</li>
|
||||
<li t-if="uid == user.id" class="nav-item">
|
||||
<a role="tab" aria-controls="votes" href="#votes" class="nav-link" data-toggle="tab">Votes</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content mt16">
|
||||
<div role="tabpanel" class="tab-pane active" id="questions">
|
||||
<div class="mb16" t-foreach="questions" t-as="question">
|
||||
<t t-call="website_forum.display_post"/>
|
||||
</div>
|
||||
</div><div role="tabpanel" class="tab-pane" id="answers">
|
||||
<div t-foreach="answers" t-as="answer">
|
||||
<t t-call="website_forum.display_post_answer"/>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="karma">
|
||||
<h1>Karma</h1>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="badges">
|
||||
<t t-call="website_forum.user_badges"/>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="favourite_question" t-if="uid == user.id">
|
||||
<div t-foreach="favourite" t-as="question">
|
||||
<t t-call="website_forum.display_post"/>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="followed_question" t-if="uid == user.id">
|
||||
<div t-foreach="followed" t-as="question" class="mb16">
|
||||
<t t-call="website_forum.display_post"/>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="votes" t-if="uid == user.id">
|
||||
<t t-call="website_forum.user_votes"/>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="activity" t-if="uid == user.id">
|
||||
<div t-foreach="activities" t-as="activity" class="card mb-2">
|
||||
<div class="card-body">
|
||||
<span t-esc="activity.subtype_id.name" class="badge badge-info mr-2 mt-1"/>
|
||||
<span t-field="activity.date" t-options='{"format": "short"}' class="mr-2"/>
|
||||
<t t-set="post" t-value="posts[activity.res_id]"/>
|
||||
<span t-if="post[1]">
|
||||
<a t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(post[0]) }#answer-#{ str(post[1].id) }">
|
||||
<span t-esc="post[0].name"/>
|
||||
</a>
|
||||
</span>
|
||||
<span t-if="not post[1]">
|
||||
<a t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(post[0]) }">
|
||||
<span t-esc="post[0].name"/>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="user_badges">
|
||||
<table class="table mt32 mb64">
|
||||
<tr t-foreach="user.badge_ids" t-as="badge">
|
||||
<td>
|
||||
<span class="badge badge-pill float-left">
|
||||
<span t-if="badge.level == 'gold'"><i class="fa fa-trophy badge-gold"/> <b t-field="badge.badge_id.name" class="text-white bg-gold badge-pill"/></span>
|
||||
<span t-if="badge.level == 'silver'"><i class="fa fa-trophy badge-silver"/> <b t-field="badge.badge_id.name" class="text-white bg-silver badge-pill"/></span>
|
||||
<span t-if="badge.level == 'bronze'"><i class="fa fa-trophy badge-bronze"/> <b t-field="badge.badge_id.name" class="text-white bg-bronze badge-pill"/></span>
|
||||
</span>
|
||||
</td><td>
|
||||
<b t-esc="badge.badge_id.stat_count_distinct"/>
|
||||
<i class="text-muted">awarded users</i>
|
||||
</td><td>
|
||||
<span t-field="badge.badge_id.description"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="mb16" t-if="not user.badge_ids">
|
||||
<b>No badge yet!</b><br/>
|
||||
<a t-attf-href="/forum/#{ slug(forum) }/badge" class="fa fa-arrow-right"> Check available badges</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="user_votes">
|
||||
<div t-foreach="vote_post" t-as="vote">
|
||||
<t t-esc="vote.post_id.create_date"/>
|
||||
<span t-if="vote.vote == '1'" class="fa fa-thumbs-up text-success" style="margin-left:30px" role="img" aria-label="Positive vote" title="Positive vote"/>
|
||||
<span t-if="vote.vote == '-1'" class="fa fa-thumbs-down text-warning" style="margin-left:30px" role="img" aria-label="Negative vote" title="Negative vote"/>
|
||||
<t t-if="vote.post_id.parent_id">
|
||||
<a t-attf-href="/forum/#{ slug(forum) }/question/#{ vote.post_id.parent_id.id }/#answer-#{ vote.post_id.id }" t-esc="vote.post_id.parent_id.name" style="margin-left:10px"/>
|
||||
</t>
|
||||
<t t-if="not vote.post_id.parent_id">
|
||||
<a t-attf-href="/forum/#{ slug(forum) }/question/#{ vote.post_id.id }" style=" color:black;margin-left:10px" t-esc="vote.post_id.name"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="mb16" t-if="not vote_post">
|
||||
<b>No vote given by you yet!</b>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- User Navbar -->
|
||||
<template id="user_navbar_inherit_website_forum" inherit_id="website.user_navbar">
|
||||
<xpath expr="//div[@id='o_new_content_menu_choices']//div[@name='module_website_forum']" position="attributes">
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo><data>
|
||||
<!--Private profile-->
|
||||
<template id="private_profile" inherit_id="website_profile.private_profile">
|
||||
<xpath expr="//div[@id='private_profile_return_link_container']" position="inside">
|
||||
<t t-if="request.params.get('forum_id')">
|
||||
<a t-attf-href="/forum/forum-#{request.params.get('forum_id')}">Return to the forum.</a>
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="user_profile_content" inherit_id="website_profile.user_profile_content">
|
||||
<xpath expr="//tr[@id='profile_abstract_info_company']" position="after">
|
||||
<t t-if="forum and (up_votes or down_votes)">
|
||||
<tr id="profile_abstract_info_company">
|
||||
<th style="width: 30%"><b class="text-muted">Votes</b></th>
|
||||
<th style="width: 70%">
|
||||
<span>
|
||||
<i class="fa fa-thumbs-up text-success" role="img" aria-label="Positive votes" title="Positive votes"/>
|
||||
<b t-esc="up_votes"/>
|
||||
<i class="fa fa-thumbs-down text-danger ml-3" role="img" aria-label="Negative votes" title="Negative votes"/>
|
||||
<b t-esc="down_votes"/>
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath expr="//ul[@id='profile_extra_info_tablist']" position="inside">
|
||||
<t t-if="forum">
|
||||
<li class="nav-item">
|
||||
<a role="tab" aria-controls="questions" href="#questions" class="nav-link o_wprofile_navlink" data-toggle="tab"><t t-esc="count_questions"/> Questions</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a role="tab" aria-controls="answers" href="#answers" class="nav-link o_wprofile_navlink" data-toggle="tab"><t t-esc="count_answers"/> Answers</a>
|
||||
</li>
|
||||
<li t-if="uid == user.id" class="nav-item">
|
||||
<a role="tab" aria-controls="activity" href="#activity" class="nav-link o_wprofile_navlink" data-toggle="tab">Activity</a>
|
||||
</li>
|
||||
<li t-if="uid == user.id" class="nav-item">
|
||||
<a role="tab" aria-controls="votes" href="#votes" class="nav-link o_wprofile_navlink" data-toggle="tab">Votes</a>
|
||||
</li>
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath expr="//div[@id='profile_extra_info_tabcontent']" position="inside">
|
||||
<t t-if="forum">
|
||||
<div role="tabpanel" class="tab-pane" id="questions">
|
||||
<div class="mb32">
|
||||
<h4><i class="fa fa-question"/> Questions</h4>
|
||||
<t t-if="questions">
|
||||
<div class="mb16" t-foreach="questions" t-as="question">
|
||||
<t t-call="website_forum.display_post"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<b>No question posted yet.</b>
|
||||
</t>
|
||||
</div>
|
||||
<t t-if="favourite">
|
||||
<div class="mb32">
|
||||
<h4><i class="fa fa-star"/> Favourite Questions</h4>
|
||||
<div t-foreach="favourite" t-as="question">
|
||||
<t t-call="website_forum.display_post"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="followed">
|
||||
<div class="mb32">
|
||||
<h4><i class="fa fa-eye"/> Followed Questions</h4>
|
||||
<div t-foreach="followed" t-as="question" class="mb16">
|
||||
<t t-call="website_forum.display_post"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="answers">
|
||||
<div class="mb32">
|
||||
<h4><i class="fa fa-reply"/> Answers</h4>
|
||||
<t t-if="answers">
|
||||
<div t-foreach="answers" t-as="answer">
|
||||
<t t-call="website_forum.display_post_answer"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<b>No answer posted yet.</b>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="votes" t-if="uid == user.id">
|
||||
<div class="mb32">
|
||||
<h4><i class="fa fa-thumbs-up"/> Votes</h4>
|
||||
<t t-call="website_forum.user_votes"/>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="activity" t-if="uid == user.id">
|
||||
<div class="mb32">
|
||||
<h4><i class="fa fa-history"/> Activities</h4>
|
||||
<t t-call="website_forum.display_activities"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="display_activities" name="Forum Profile Activities">
|
||||
<t t-if="activities">
|
||||
<div t-foreach="activities" t-as="activity" class="card mb-2">
|
||||
<div class="card-body">
|
||||
<span t-esc="activity.subtype_id.name" class="badge badge-info mr-2 mt-1"/>
|
||||
<span t-field="activity.date" t-options='{"format": "short"}' class="mr-2"/>
|
||||
<t t-set="post" t-value="posts[activity.res_id]"/>
|
||||
<span t-if="post[1]">
|
||||
<a t-attf-href="/forum/#{ slug(post[0].forum_id) }/question/#{ slug(post[0]) }#answer-#{ str(post[1].id) }">
|
||||
<span t-esc="post[0].name"/>
|
||||
</a>
|
||||
</span>
|
||||
<span t-if="not post[1]">
|
||||
<a t-attf-href="/forum/#{ slug(post[0].forum_id) }/question/#{ slug(post[0]) }">
|
||||
<span t-esc="post[0].name"/>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<b>No activities yet!</b>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="user_votes" name="Forum User Votes">
|
||||
<div t-foreach="vote_post" t-as="vote">
|
||||
<t t-esc="vote.post_id.create_date"/>
|
||||
<span t-if="vote.vote == '1'" class="fa fa-thumbs-up text-success" style="margin-left:30px" role="img" aria-label="Positive vote" title="Positive vote"/>
|
||||
<span t-if="vote.vote == '-1'" class="fa fa-thumbs-down text-warning" style="margin-left:30px" role="img" aria-label="Negative vote" title="Negative vote"/>
|
||||
<t t-if="vote.post_id.parent_id">
|
||||
<a t-attf-href="/forum/#{ slug(vote.post_id.forum_id) }/question/#{ vote.post_id.parent_id.id }/#answer-#{ vote.post_id.id }" t-esc="vote.post_id.parent_id.name" style="margin-left:10px"/>
|
||||
</t>
|
||||
<t t-if="not vote.post_id.parent_id">
|
||||
<a t-attf-href="/forum/#{ slug(vote.post_id.forum_id) }/question/#{ vote.post_id.id }" style=" color:black;margin-left:10px" t-esc="vote.post_id.name"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="mb16" t-if="not vote_post">
|
||||
<b>No vote given by you yet!</b>
|
||||
</div>
|
||||
</template>
|
||||
</data></odoo>
|
||||
@@ -2,3 +2,4 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from . import controllers
|
||||
|
||||
@@ -8,11 +8,12 @@
|
||||
'summary': 'Access the website profile of the users',
|
||||
'description': """Allows to acces the website profile of the users and see their statistics (karma, badges, etc..)""",
|
||||
'depends': [
|
||||
'website',
|
||||
'website_partner',
|
||||
'gamification'
|
||||
],
|
||||
'data': [
|
||||
'views/gamification_badge_views.xml',
|
||||
'views/website_profile.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'auto_install': False,
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import main
|
||||
@@ -0,0 +1,128 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import base64
|
||||
import werkzeug.exceptions
|
||||
import werkzeug.urls
|
||||
import werkzeug.wrappers
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
from odoo.osv import expression
|
||||
|
||||
|
||||
class WebsiteProfile(http.Controller):
|
||||
# Profile
|
||||
# ---------------------------------------------------
|
||||
def _check_user_profile_access(self, user_id):
|
||||
user_sudo = request.env['res.users'].sudo().browse(user_id)
|
||||
if user_sudo.karma == 0 or not user_sudo.website_published or \
|
||||
(user_sudo.id != request.session.uid and request.env.user.karma < request.website.karma_profile_min):
|
||||
return False
|
||||
return user_sudo
|
||||
|
||||
def _prepare_user_values(self, **kwargs):
|
||||
values = {
|
||||
'user': request.env.user,
|
||||
'is_public_user': request.website.is_public_user(),
|
||||
}
|
||||
values.update(kwargs)
|
||||
return values
|
||||
|
||||
def _prepare_user_profile_parameters(self, **post):
|
||||
return post
|
||||
|
||||
def _prepare_user_profile_values(self, user, **post):
|
||||
return {
|
||||
'uid': request.env.user.id,
|
||||
'user': user,
|
||||
'main_object': user,
|
||||
'is_profile_page': True,
|
||||
'edit_button_url_param': '',
|
||||
}
|
||||
|
||||
@http.route(['/profile/user/<int:user_id>'], type='http', auth="public", website=True)
|
||||
def view_user_profile(self, user_id, **post):
|
||||
user = self._check_user_profile_access(user_id)
|
||||
if not user:
|
||||
return request.render("website_profile.private_profile")
|
||||
params = self._prepare_user_profile_parameters(**post)
|
||||
values = self._prepare_user_profile_values(user, **params)
|
||||
return request.render("website_profile.user_profile_main", values)
|
||||
|
||||
# Edit Profile
|
||||
# ---------------------------------------------------
|
||||
@http.route('/profile/edit', type='http', auth="user", website=True)
|
||||
def view_user_profile_edition(self, **kwargs):
|
||||
countries = request.env['res.country'].search([])
|
||||
values = self._prepare_user_values(searches=kwargs)
|
||||
values.update({
|
||||
'email_required': kwargs.get('email_required'),
|
||||
'countries': countries,
|
||||
'url_param': kwargs.get('url_param'),
|
||||
})
|
||||
return request.render("website_profile.user_profile_edit_main", values)
|
||||
|
||||
def _profile_edition_preprocess_values(self, user, **kwargs):
|
||||
values = {
|
||||
'name': kwargs.get('name'),
|
||||
'website': kwargs.get('website'),
|
||||
'email': kwargs.get('email'),
|
||||
'city': kwargs.get('city'),
|
||||
'country_id': int(kwargs.get('country')) if kwargs.get('country') else False,
|
||||
'website_description': kwargs.get('description'),
|
||||
}
|
||||
|
||||
if 'clear_image' in kwargs:
|
||||
values['image'] = False
|
||||
elif kwargs.get('ufile'):
|
||||
image = kwargs.get('ufile').read()
|
||||
values['image'] = base64.b64encode(image)
|
||||
|
||||
if request.uid == user.id: # the controller allows to edit only its own privacy settings; use partner management for other cases
|
||||
values['website_published'] = kwargs.get('website_published') == 'True'
|
||||
return values
|
||||
|
||||
@http.route('/profile/user/save', type='http', auth="user", methods=['POST'], website=True)
|
||||
def save_edited_profile(self, **kwargs):
|
||||
user = request.env.user
|
||||
values = self._profile_edition_preprocess_values(user, **kwargs)
|
||||
whitelisted_values = {key: values[key] for key in type(user).SELF_WRITEABLE_FIELDS if key in values}
|
||||
user.write(whitelisted_values)
|
||||
if kwargs.get('url_param'):
|
||||
return werkzeug.utils.redirect("/profile/user/%d?%s" % (user.id, kwargs['url_param']))
|
||||
else:
|
||||
return werkzeug.utils.redirect("/profile/user/%d" % user.id)
|
||||
# Ranks
|
||||
# ---------------------------------------------------
|
||||
@http.route('/profile/ranks', type='http', auth="public", website=True)
|
||||
def ranks(self, **kwargs):
|
||||
Rank = request.env['gamification.karma.rank']
|
||||
ranks = Rank.sudo().search([])
|
||||
ranks = ranks.sorted(key=lambda b: b.karma_min)
|
||||
values = {
|
||||
'ranks': ranks,
|
||||
}
|
||||
return request.render("website_profile.rank_main", values)
|
||||
|
||||
# Badges
|
||||
# ---------------------------------------------------
|
||||
def _prepare_badges_domain(self, **kwargs):
|
||||
"""
|
||||
Hook for other modules to restrict the badges showed on profile page, depending of the context
|
||||
"""
|
||||
domain = [('website_published', '=', True)]
|
||||
if 'category' in kwargs:
|
||||
domain = expression.AND([[('challenge_ids.category', '=', kwargs.get('category'))], domain])
|
||||
return domain
|
||||
|
||||
@http.route('/profile/badge', type='http', auth="public", website=True)
|
||||
def badges(self, **kwargs):
|
||||
Badge = request.env['gamification.badge']
|
||||
badges = Badge.sudo().search(self._prepare_badges_domain(**kwargs))
|
||||
badges = sorted(badges, key=lambda b: b.stat_count_distinct, reverse=True)
|
||||
values = self._prepare_user_values(searches={'badges': True})
|
||||
values.update({
|
||||
'badges': badges,
|
||||
})
|
||||
return request.render("website_profile.badge_main", values)
|
||||
@@ -2,3 +2,4 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import gamification_badge
|
||||
from . import website
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class Website(models.Model):
|
||||
_inherit = 'website'
|
||||
|
||||
karma_profile_min = fields.Integer(string="Minimal karma to see other user's profile", default=150)
|
||||
@@ -0,0 +1,189 @@
|
||||
$gold: #ffcc00;
|
||||
$silver: #cccccc;
|
||||
$bronze: #eea91e;
|
||||
|
||||
.o_wprofile_container {
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
max-width: 1140px;
|
||||
padding: 32px 32px 0px 32px;
|
||||
}
|
||||
|
||||
.o_wprofile_header {
|
||||
margin: auto;
|
||||
background-color: #875A7B !important;
|
||||
border-bottom: 48px solid #66445e;
|
||||
.o_wprofile_header_container {
|
||||
top: 48px;
|
||||
padding-top: 0px;
|
||||
.o_wprofile_id {
|
||||
font-size: 16px;
|
||||
.o_wprofile_id_info {
|
||||
margin-right: 36px;
|
||||
}
|
||||
}
|
||||
.o_wprofile_pict {
|
||||
.o_wprofile_photo {
|
||||
width: 100%;
|
||||
max-width: 256px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_wprofile_content {
|
||||
margin: auto;
|
||||
background-color: #f2f2f2 !important;
|
||||
.o_wprofile_content_container{
|
||||
position: relative;
|
||||
top: -80px;
|
||||
.o_wprofile_navtabs_container {
|
||||
.o_wprofile_nav_tabs {
|
||||
border-bottom: 0px;
|
||||
}
|
||||
.o_wprofile_navlink {
|
||||
color: white;
|
||||
border-radius: 0px;
|
||||
height: 48px;
|
||||
font-weight: bold;
|
||||
padding-top: 12px;
|
||||
border-left: 2px solid #875A7B;
|
||||
text-align: center;
|
||||
width: 120px;
|
||||
&:hover {
|
||||
border: 0px;
|
||||
border-radius: 0px;
|
||||
background-color: #875A7B;
|
||||
}
|
||||
}
|
||||
.o_wprofile_navlink.active {
|
||||
color: #212529;
|
||||
background-color: #f2f2f2;
|
||||
border: 0px;
|
||||
}
|
||||
}
|
||||
.o_wprofile_detail {
|
||||
.o_wprofile_content_abstract {
|
||||
top: 1px;
|
||||
width: 256px;
|
||||
max-width: 256px;
|
||||
margin-left: 15px;
|
||||
table {
|
||||
width: 100%;
|
||||
tr {
|
||||
line-height: 32px;
|
||||
}
|
||||
th {
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
.o_wprofile_rank {
|
||||
background-color: white;
|
||||
}
|
||||
.o_wprofile_abstract_info {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
.o_wprofile_content_detail {
|
||||
h4 {
|
||||
border-bottom: 1px solid lightgrey;
|
||||
color: grey;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.o_user_badges {
|
||||
padding: 8px;
|
||||
div {
|
||||
padding: 8px;
|
||||
}
|
||||
img {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rank
|
||||
.o_wprofile_ranks_presentation {
|
||||
background-color: lightgray;
|
||||
.o_wprofile_rank_tile {
|
||||
width: 20%;
|
||||
div {
|
||||
text-align:center;
|
||||
img {
|
||||
height: 128px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_wprofile_next_rank_card {
|
||||
div {
|
||||
text-align: center;
|
||||
img {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
}
|
||||
border-bottom: 1px solid lightgray;
|
||||
.o_wprofile_next_rank_progress {
|
||||
background-color: lightgray;
|
||||
.o_wprofile_next_rank_progress_bar {
|
||||
background-color: #00A09D !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Edit Profile button
|
||||
.o_wprofile_edit {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
// Badges
|
||||
.badge-gold {
|
||||
color: $gold;
|
||||
}
|
||||
.bg-gold {
|
||||
background-color: $gold;
|
||||
}
|
||||
|
||||
.badge-silver {
|
||||
color: $silver;
|
||||
}
|
||||
.bg-silver {
|
||||
background-color: $silver;
|
||||
}
|
||||
|
||||
.badge-bronze {
|
||||
color: $bronze;
|
||||
}
|
||||
.bg-bronze {
|
||||
background-color: $bronze;
|
||||
}
|
||||
|
||||
// Other stuffs
|
||||
.o_wprofile_bb_grey {
|
||||
border-bottom: 2px solid lightgrey;
|
||||
}
|
||||
|
||||
.country_flag {
|
||||
display: inline-block;
|
||||
margin-left: 2px;
|
||||
max-height: 13px;
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
.o_wprofile_img_small {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
.o_wprofile_img_medium {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo><data>
|
||||
<template id="assets_frontend" inherit_id="website.assets_frontend">
|
||||
<xpath expr="link[last()]" position="after">
|
||||
<link rel="stylesheet" type="text/scss" href="/website_profile/static/src/scss/website_profile.scss"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<!-- Edit Profile Page -->
|
||||
<template id="user_profile_edit_main" name="Edit Profile">
|
||||
<t t-call="website.layout">
|
||||
<div class="container mt16 mb32">
|
||||
<t t-call="website_profile.user_profile_edit_content">
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="user_profile_edit_content" name="Edit Profile">
|
||||
<h1 class="o_page_header">Edit Profile</h1>
|
||||
<div>
|
||||
<form t-attf-action="/profile/user/save" method="post" role="form" class="js_website_submit_form row" enctype="multipart/form-data">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="file" class="d-none o_forum_file_upload" name="ufile" accept="image/*"/>
|
||||
<input type="hidden" name="url_param" t-att-value="request.params.get('url_param')"/>
|
||||
<div class="col-3">
|
||||
<div class="card o_card_people">
|
||||
<div class="card-body">
|
||||
<img class="w-100 mb-3" t-attf-src="/web/image/res.users/#{user.id}/image_medium"/>
|
||||
<div class="text-center">
|
||||
<a href="#" class="o_forum_profile_pic_edit btn btn-primary" aria-label="Edit">
|
||||
<i class="fa fa-pencil fa-1g float-sm-none float-md-left" title="Edit"></i>
|
||||
</a>
|
||||
<a href="#" title="Clear" aria-label="Clear" class="btn border-primary">
|
||||
<i class="fa fa-trash-o float-sm-none float-md-right o_forum_profile_pic_clear"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="form-group mt-3 mb-0 pt-2 border-top">
|
||||
<label class="text-primary" for="user_website_published" t-if="user.id == uid"><b>Public profile</b></label>
|
||||
<div class=" mb-0 float-right" t-if="user.id == uid">
|
||||
<input type="checkbox" class="mt8" name="website_published" id="user_website_published" value="True" t-if="not user.website_published"/>
|
||||
<input type="checkbox" class="mt8" name="website_published" id="user_website_published" value="True" checked="checked" t-if="user.website_published"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<input name="user_id" t-att-value="user.id" type="hidden"/>
|
||||
<div class="form-group col-12">
|
||||
<label class="text-primary mb-1 d-block" for="user_name"><b>Real name</b></label>
|
||||
<div>
|
||||
<input type="text" class="form-control" name="name" id="user_name" required="True" t-attf-value="#{user.name}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-6">
|
||||
<label class="mb-1 text-primary" for="user_website"><b>Website</b></label>
|
||||
<div>
|
||||
<input type="text" class="form-control" name="website" id="user_website" t-attf-value="#{user.partner_id.website or ''}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-6">
|
||||
<div t-if="email_required" class="alert alert-danger alert-dismissable oe_email_required" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert">x</button>
|
||||
<p>Please enter a valid email address in order to receive notifications from answers or comments.</p>
|
||||
</div>
|
||||
<label class="mb-1 text-primary" for="user_email"><b>Email</b></label>
|
||||
<div>
|
||||
<input type="text" class="form-control" name="email" id="user_email" required="True" t-attf-value="#{user.partner_id.email}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-6">
|
||||
<label class="mb-1 text-primary" for="user_city"><b>City</b></label>
|
||||
<div>
|
||||
<input type="text" class="form-control" name="city" id="user_city" t-attf-value="#{user.partner_id.city or ''}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-6">
|
||||
<label class="mb-1 text-primary"><b>Country</b></label>
|
||||
<div>
|
||||
<select class="form-control" name="country">
|
||||
<option value="">Country...</option>
|
||||
<t t-foreach="countries or []" t-as="country">
|
||||
<option t-att-value="country.id" t-att-selected="country.id == user.partner_id.country_id.id"><t t-esc="country.name"/></option>
|
||||
</t>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-12">
|
||||
<label class="mb-1 text-primary" for="description"><b>Biography</b></label>
|
||||
<textarea name="description" id="description" style="min-height: 120px" required="True"
|
||||
class="form-control load_editor"><t t-esc="user.partner_id.website_description"/></textarea>
|
||||
</div>
|
||||
<div class="col">
|
||||
<button type="submit" class="btn btn-primary btn">Update</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Profile Page -->
|
||||
<template id="user_profile_main" name="Profile Page">
|
||||
<t t-call="website.layout">
|
||||
<t t-call="website_profile.user_profile_header"></t>
|
||||
<t t-call="website_profile.user_profile_content"></t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="user_profile_header" name="Profile Page Header">
|
||||
<div class="row o_wprofile_header">
|
||||
<div class="col-lg-12 o_wprofile_container o_wprofile_header_container">
|
||||
<div class="row">
|
||||
<div class="col o_wprofile_pict">
|
||||
<img class="o_wprofile_photo mr-3" t-attf-src="/web/image/res.users/#{user.id}/image"/>
|
||||
</div>
|
||||
<div class="col-8 o_wprofile_id">
|
||||
<a id="edit_profile_button" class="btn btn-primary o_wprofile_edit"
|
||||
t-if="request.env.user == user"
|
||||
t-attf-href="/profile/edit?url_param=#{edit_button_url_param}"><b>EDIT PROFILE</b></a>
|
||||
<div class="row mb32">
|
||||
<h3 class="o_card_people_name text-white"><b t-field="user.name"/></h3>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="o_wprofile_id_info">
|
||||
<t t-if="user.company_id">
|
||||
<span class="text-white">
|
||||
<i class="fa fa-building-o text-white mr-2"/><span t-field="user.company_id"/>
|
||||
</span>
|
||||
</t>
|
||||
</div>
|
||||
<div class="o_wprofile_id_info">
|
||||
<t t-if="user.city or user.country_id">
|
||||
<i class="fa fa-map-marker text-white mr-2"/>
|
||||
<span class="text-white">
|
||||
<span t-field="user.city"/><t t-if="user.country_id"> (</t><span t-field="user.country_id"/><t t-if="user.country_id">)</t>
|
||||
</span>
|
||||
</t>
|
||||
</div>
|
||||
<div class="o_wprofile_id_info">
|
||||
<t t-if="user.website">
|
||||
<span class="text-white">
|
||||
<i class="fa fa-globe text-white mr-2"/><span t-field="user.website"/>
|
||||
</span>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="user_profile_content" name="Profile Page Content">
|
||||
<div class="row o_wprofile_content">
|
||||
<div class="col-lg-12 o_wprofile_container o_wprofile_content_container">
|
||||
<div class="row">
|
||||
<div class="col o_wprofile_navtabs_blank"></div>
|
||||
<div class="col-8 o_wprofile_navtabs_container">
|
||||
<ul class="nav nav-tabs o_wprofile_nav_tabs" role="tablist" id="profile_extra_info_tablist">
|
||||
<li class="nav-item" id="profile_nav_tab_about">
|
||||
<a role="tab" aria-controls="about" href="#profile_tab_content_about" class="nav-link o_wprofile_navlink active" data-toggle="tab">About</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row o_wprofile_detail">
|
||||
<div class="col o_wprofile_content_abstract">
|
||||
<t t-if="user.rank_id">
|
||||
<div class="row">
|
||||
<div class="col-lg-12 pt16 o_wprofile_rank">
|
||||
<table><tbody><tr>
|
||||
<th><b class="text-muted">Current <a href="/profile/ranks">Rank</a> : </b></th>
|
||||
<th><span class="float-right" t-field="user.rank_id"/></th>
|
||||
</tr></tbody></table>
|
||||
<t t-call="website_profile.profile_next_rank_card"></t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<div class="row">
|
||||
<div class="col-lg-12 pt16 pb16 o_wprofile_abstract_info o_wprofile_bb_grey">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th style="width: 30%"><b class="text-muted">Joined</b></th>
|
||||
<th style="width: 70%"><span t-field="user.create_date" t-options='{"format": "d MMM Y"}'/></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width: 30%"><b class="text-muted">Badges</b></th>
|
||||
<th style="width: 70%">
|
||||
<t t-if="user.badge_ids">
|
||||
<span t-esc="len(user.badge_ids)"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span>0</span>
|
||||
</t>
|
||||
</th>
|
||||
</tr>
|
||||
<t t-if="user.website">
|
||||
<tr>
|
||||
<th style="width: 30%"><b class="text-muted">Website</b></th>
|
||||
<th style="width: 70%"><span t-field="user.website"/></th>
|
||||
</tr>
|
||||
</t>
|
||||
<t t-if="user.city or user.country_id">
|
||||
<tr>
|
||||
<th style="width: 30%"><b class="text-muted">Location</b></th>
|
||||
<th style="width: 70%">
|
||||
<span t-field="user.city"/><t t-if="user.country_id"> (</t><span t-field="user.country_id"/><t t-if="user.country_id">)</t>
|
||||
</th>
|
||||
</tr>
|
||||
</t>
|
||||
<tr id="profile_abstract_info_company">
|
||||
<th style="width: 30%"><b class="text-muted">Company</b></th>
|
||||
<th style="width: 70%"><span t-field="user.company_id"/></th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-8 mt24 ml32 o_wprofile_content_detail">
|
||||
<div class="tab-content mt16" id="profile_extra_info_tabcontent">
|
||||
<div role="tabpanel" class="tab-pane active" id="profile_tab_content_about">
|
||||
<div id="profile_about_badge" class="mb32">
|
||||
<h4><i class="fa fa-certificate"/> Badges</h4>
|
||||
<t t-call="website_profile.user_badges"></t>
|
||||
</div>
|
||||
<div t-if="user.partner_id.website_description" class="mb32">
|
||||
<h4><i class="fa fa-pencil"/> Biography</h4>
|
||||
<span t-field="user.partner_id.website_description"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="profile_next_rank_card" name="Profile Next Rank Card">
|
||||
<div class="card-body o_wprofile_next_rank_card mt-2 pt-2">
|
||||
<div><img class="o_wprofile_img_medium" t-attf-src="/web/image/gamification.karma.rank/#{user.next_rank_id.id}/image_medium"/></div>
|
||||
<div><span class="text-center"><b>"<span t-field="user.next_rank_id.name"/>"</b></span></div>
|
||||
<div><span class="text-center"><b class="text-primary" t-field="user.karma"/>/<b t-field="user.next_rank_id.karma_min"/> xp</span></div>
|
||||
<div class="progress o_wprofile_next_rank_progress">
|
||||
<t t-if="user.next_rank_id.karma_min - user.rank_id.karma_min > 0">
|
||||
<div class="progress-bar o_wprofile_next_rank_progress_bar" style="background-color: #00A09D !important"
|
||||
t-att-style="'width: '+ str(100*(user.karma - user.rank_id.karma_min)/(user.next_rank_id.karma_min - user.rank_id.karma_min))+'%'"></div>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div class="progress-bar o_wprofile_next_rank_progress_bar" style="background-color: #00A09D !important"
|
||||
t-att-style="'width: 0%'"></div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="user_badges" name="User Bagdes">
|
||||
<t t-if="user.badge_ids">
|
||||
<div class="row">
|
||||
<div class="col-lg-3 o_user_badges" t-foreach="user.badge_ids" t-as="badge">
|
||||
<div class="card-body o_wprofile_bb_grey">
|
||||
<table><tbody><tr>
|
||||
<th>
|
||||
<img class="o_wprofile_img_thumb" t-attf-src="/web/image/gamification.badge/#{badge.badge_id.id}/image_small"/>
|
||||
</th>
|
||||
<th style="">
|
||||
<b t-field="badge.badge_id.name"/>
|
||||
</th>
|
||||
</tr></tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<div class="mb16" t-if="not user.badge_ids">
|
||||
<b>No badge yet!</b><br/>
|
||||
<t t-if="badge_category">
|
||||
<a t-attf-href="/profile/badge?category=#{badge_category}" class="fa fa-arrow-right"> Check available badges</a>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<a t-attf-href="/profile/badge" class="fa fa-arrow-right"> Check available badges</a>
|
||||
</t>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- About Rank Page -->
|
||||
<template id="rank_main" name="Ranks Page">
|
||||
<t t-call="website.layout">
|
||||
<div class="container mb32 mt48">
|
||||
<h1 class="o_page_header">
|
||||
Ranks & Badges
|
||||
</h1>
|
||||
<p>
|
||||
Do not stop learning with Odoo. Collects points on the forum or the eLearning platform. Those points will make you reach new ranks.
|
||||
</p>
|
||||
<p>
|
||||
<b>How do I earn badges?</b> When you finish a course, reach milestones, you're awarded badges.
|
||||
</p>
|
||||
<p>
|
||||
<b>How do I score more points?</b> You can score more points by answering quizzes at the end of each course content. Points can also be earned on the forum. Follow this link to the guidelines of the forum.
|
||||
</p>
|
||||
</div>
|
||||
<t t-call="website_profile.rank_content">
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="rank_content" name="Rank Page content">
|
||||
<div class="o_wprofile_ranks_presentation mt16 mb32 pt16 pb48">
|
||||
<div class="container mb32 mt48">
|
||||
<h3>Ranks</h3>
|
||||
<div class="row">
|
||||
<div class="o_wprofile_rank_tile" t-foreach="ranks" t-as="rank">
|
||||
<div><img class="mb-3" t-attf-src="/web/image/gamification.karma.rank/#{rank.id}/image"/></div>
|
||||
<div><b t-field="rank.name"/></div>
|
||||
<div><span style="color: #00A09D;"><span t-field="rank.karma_min"/> point<span t-if="rank.karma_min">s</span></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- About Badge Page -->
|
||||
<template id="badge_main" name="Badges Page">
|
||||
<t t-call="website.layout">
|
||||
<div class="container mb32 mt48">
|
||||
<t t-call="website_profile.badge_content">
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="badge_content" name="Badges Page content">
|
||||
<h1 class="o_page_header">
|
||||
Badges
|
||||
</h1>
|
||||
<p>
|
||||
Besides gaining reputation with your questions and answers,
|
||||
you receive badges for being especially helpful. Badges
|
||||
appear on your profile page, and your posts.
|
||||
</p>
|
||||
<table class="table mt32 mb64">
|
||||
<tr t-foreach="badges" t-as="badge">
|
||||
<td>
|
||||
<span class="badge badge-pill float-left">
|
||||
<span t-if="badge.level == 'gold'"><i class="fa fa-trophy badge-gold"/> <b t-field="badge.name" class="text-white bg-gold badge-pill"/></span>
|
||||
<span t-if="badge.level == 'silver'"><i class="fa fa-trophy badge-silver"/> <b t-field="badge.name" class="text-white bg-silver badge-pill"/></span>
|
||||
<span t-if="badge.level == 'bronze'"><i class="fa fa-trophy badge-bronze"/> <b t-field="badge.name" class="text-white bg-bronze badge-pill"/></span>
|
||||
</span>
|
||||
</td><td>
|
||||
<b t-esc="badge.stat_count_distinct"/>
|
||||
<i class="text-muted"> awarded users</i>
|
||||
</td><td>
|
||||
<span t-field="badge.description"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<!--Private profile-->
|
||||
<template id="private_profile" name="Private Profile Page">
|
||||
<t t-call="website.layout">
|
||||
<div class="container mb32 mt48">
|
||||
<h1 class="mt32">This profile is private!</h1>
|
||||
<div id="private_profile_return_link_container">
|
||||
<p><a t-attf-href="/">Return to the website.</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
</data></odoo>
|
||||
@@ -33,6 +33,7 @@ Share and Publish Videos, Presentations and Documents'
|
||||
'views/slide_channel_tag_views.xml',
|
||||
'views/slide_channel_invite_views.xml',
|
||||
'views/website_slides_menu_views.xml',
|
||||
'views/website_slides_templates_profile.xml',
|
||||
'data/website_slides_ir_data.xml',
|
||||
'data/mail_data.xml',
|
||||
'data/slide_data.xml',
|
||||
|
||||
@@ -11,13 +11,14 @@ from odoo.http import request
|
||||
from odoo.osv import expression
|
||||
|
||||
from odoo.addons.http_routing.models.ir_http import slug
|
||||
from odoo.addons.website_profile.controllers.main import WebsiteProfile
|
||||
from odoo.addons.website.models.ir_http import sitemap_qs2dom
|
||||
from odoo.tools import html2plaintext
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WebsiteSlides(http.Controller):
|
||||
class WebsiteSlides(WebsiteProfile):
|
||||
_slides_per_page = 12
|
||||
_slides_per_list = 20
|
||||
_order_by_criterion = {
|
||||
@@ -44,9 +45,15 @@ class WebsiteSlides(http.Controller):
|
||||
request.session['viewed_slides'] = viewed_slides
|
||||
else:
|
||||
slide.action_set_viewed()
|
||||
|
||||
return True
|
||||
|
||||
def _prepare_user_values(self, **kwargs):
|
||||
values = super(WebsiteSlides, self)._prepare_user_values(**kwargs)
|
||||
channel = self._get_channels(**kwargs)
|
||||
if channel:
|
||||
values['channel'] = channel
|
||||
return values
|
||||
|
||||
def _get_slide_detail(self, slide):
|
||||
most_viewed_slides = slide.get_most_viewed_slides(self._slides_per_list)
|
||||
related_slides = slide.get_related_slides(self._slides_per_list)
|
||||
@@ -95,7 +102,6 @@ class WebsiteSlides(http.Controller):
|
||||
|
||||
'''/slides/<model("slide.channel"):channel>/tag/<model("slide.tag"):tag>''',
|
||||
'''/slides/<model("slide.channel"):channel>/tag/<model("slide.tag"):tag>/page/<int:page>''',
|
||||
|
||||
'''/slides/<model("slide.channel"):channel>/category/<model("slide.category"):category>''',
|
||||
'''/slides/<model("slide.channel"):channel>/category/<model("slide.category"):category>/page/<int:page>''',
|
||||
|
||||
@@ -394,3 +400,37 @@ class WebsiteSlides(http.Controller):
|
||||
except AccessError: # TODO : please, make it clean one day, or find another secure way to detect
|
||||
# if the slide can be embedded, and properly display the error message.
|
||||
return request.render('website_slides.embed_slide_forbidden', {})
|
||||
|
||||
# Profile
|
||||
# ---------------------------------------------------
|
||||
def _get_channels(self, **kwargs):
|
||||
channels = []
|
||||
if kwargs.get('channel'):
|
||||
channels = kwargs['channel']
|
||||
elif kwargs.get('channel_id'):
|
||||
channels = request.env['slide.channel'].browse(int(kwargs['channel_id']))
|
||||
return channels
|
||||
|
||||
def _prepare_user_slides_profile(self, user):
|
||||
courses = request.env['slide.channel.partner'].sudo().search([('partner_id', '=', user.partner_id.id)]).mapped('channel_id')
|
||||
values = {
|
||||
'uid': request.env.user.id,
|
||||
'user': user,
|
||||
'main_object': user,
|
||||
'courses': courses,
|
||||
'count_courses': len(courses),
|
||||
'is_profile_page': True,
|
||||
'badge_category': 'slides',
|
||||
}
|
||||
return values
|
||||
|
||||
def _prepare_user_profile_values(self, user, **post):
|
||||
values = super(WebsiteSlides, self)._prepare_user_profile_values(user, **post)
|
||||
if post.get('channel_id'):
|
||||
values.update({'edit_button_url_param': 'channel_id=' + str(post['channel_id'])})
|
||||
channels = self._get_channels(**post)
|
||||
if not channels:
|
||||
channels = request.env['slide.channel'].search([])
|
||||
values.update(self._prepare_user_values(channel=channels[0] if len(channels) == 1 else True, **post))
|
||||
values.update(self._prepare_user_slides_profile(user))
|
||||
return values
|
||||
|
||||
@@ -7,3 +7,4 @@ from . import slide_channel_tag
|
||||
from . import slide_channel_invite
|
||||
from . import res_config_settings
|
||||
from . import website
|
||||
from . import res_users
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class Users(models.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
def get_gamification_redirection_data(self):
|
||||
res = super(Users, self).get_gamification_redirection_data()
|
||||
res.append({
|
||||
'url': '/slides',
|
||||
'label': 'See our eLearning'
|
||||
})
|
||||
return res
|
||||
@@ -6,7 +6,6 @@ from PIL import Image
|
||||
import base64
|
||||
import datetime
|
||||
import io
|
||||
import json
|
||||
import re
|
||||
|
||||
from werkzeug import urls
|
||||
@@ -121,6 +120,7 @@ class Slide(models.Model):
|
||||
is_preview = fields.Boolean('Always visible', default=False)
|
||||
completion_time = fields.Float('# Hours', default=1, digits=(10, 4))
|
||||
image = fields.Binary('Image', attachment=True)
|
||||
# TODO DBE : review image medium and small - using standard image calls (see web/image/.. route)
|
||||
image_medium = fields.Binary('Medium', compute="_get_image", store=True, attachment=True)
|
||||
image_thumb = fields.Binary('Thumbnail', compute="_get_image", store=True, attachment=True)
|
||||
# subscribers
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
.o_wprofile_slides_card {
|
||||
padding: 8px;
|
||||
max-height: 68px;
|
||||
}
|
||||
.o_wprofile_slides_title {
|
||||
top: -15px;
|
||||
padding-left: 76px;
|
||||
}
|
||||
|
||||
// Quest - Course Card
|
||||
.o_wprofile_slides_course_card_body {
|
||||
cursor: pointer;
|
||||
max-height: 64px;
|
||||
.o_wprofile_slides_course_img {
|
||||
position: absolute;
|
||||
padding: 0px;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
width: 66px;
|
||||
height: 66px;
|
||||
}
|
||||
.o_wprofile_slide_course_rating {
|
||||
position: absolute;
|
||||
top: 13px;
|
||||
right: 16px;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
<template id="assets_frontend" inherit_id="website.assets_frontend" name="Slide">
|
||||
<xpath expr="//link[last()]" position="after">
|
||||
<link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/website_slides.scss" t-ignore="true"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/website_slides_profile.scss"/>
|
||||
</xpath>
|
||||
<xpath expr="//script[last()]" position="after">
|
||||
<script type="text/javascript" src="/website_slides/static/src/js/slides.js"/>
|
||||
|
||||
@@ -107,34 +107,43 @@
|
||||
<div class="row">
|
||||
<!-- Name and breadcrumb -->
|
||||
<div class="float-left col-lg-8 col-md-12 col-12 mt16 mb8">
|
||||
<h3 class="mt0">
|
||||
<a t-attf-href="/slides/#{slug(channel)}" t-esc="channel.name"/>
|
||||
<span t-if="not channel.is_member" class="o_wslides_join_channel">
|
||||
<a role="button" class="btn btn-primary o_wslides_join_channel_link"
|
||||
title="Join Channel" aria-label="Join Channel" href="#"
|
||||
t-att-data-channel-id="channel.id">
|
||||
<i class="fa fa-chevron-right"></i> Join
|
||||
</a>
|
||||
</span>
|
||||
<span t-if="channel.is_member">
|
||||
<a role="button" class="btn btn-primary o_wslides_join_channel_link"
|
||||
title="Join Channel" aria-label="Join Channel" href="#">
|
||||
<i class="fa fa-chevron-right"></i> Joined
|
||||
</a>
|
||||
</span>
|
||||
<t t-if="category">
|
||||
/ <a t-attf-href="/slides/#{slug(channel)}/category/#{slug(category)}" t-esc="category.name"/>
|
||||
</t>
|
||||
<t t-if="channel.can_upload">
|
||||
<a role="button" class="btn btn-primary oe_slide_js_upload" title="Upload Presentation"
|
||||
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"></i> Upload
|
||||
</a>
|
||||
</t>
|
||||
</h3>
|
||||
<div class="row">
|
||||
<h3 class="mt0">
|
||||
<a t-attf-href="/slides/#{slug(channel)}" t-esc="channel.name"/>
|
||||
<span t-if="not channel.is_member" class="o_wslides_join_channel">
|
||||
<a role="button" class="btn btn-primary o_wslides_join_channel_link"
|
||||
title="Join Channel" aria-label="Join Channel" href="#"
|
||||
t-att-data-channel-id="channel.id">
|
||||
<i class="fa fa-chevron-right"></i> Join
|
||||
</a>
|
||||
</span>
|
||||
<span t-if="channel.is_member">
|
||||
<a role="button" class="btn btn-primary o_wslides_join_channel_link"
|
||||
title="Join Channel" aria-label="Join Channel" href="#">
|
||||
<i class="fa fa-chevron-right"></i> Joined
|
||||
</a>
|
||||
</span>
|
||||
<t t-if="category">
|
||||
/ <a t-attf-href="/slides/#{slug(channel)}/category/#{slug(category)}" t-esc="category.name"/>
|
||||
</t>
|
||||
<t t-if="channel.can_upload">
|
||||
<a role="button" class="btn btn-primary oe_slide_js_upload" title="Upload Presentation"
|
||||
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"></i> Upload
|
||||
</a>
|
||||
</t>
|
||||
</h3>
|
||||
<ul class="navbar-nav ml-auto float-right pt-2">
|
||||
<li class="nav-item">
|
||||
<a t-attf-href="/profile/user/#{user.id}?channel_id=#{channel.id}" t-attf-class="text-dark font-weight-bold nav-link}">
|
||||
<i class="fa fa-diamond text-secondary" /> <t t-esc="user_id.karma"/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Search box -->
|
||||
<div class="col-lg-4 col-md-12 col-12 pul-right mt16 mb8">
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" ?>
|
||||
<odoo><data>
|
||||
<!--Private profile-->
|
||||
<template id="private_profile" inherit_id="website_profile.private_profile">
|
||||
<xpath expr="//div[@id='private_profile_return_link_container']" position="inside">
|
||||
<t t-if="request.params.get('channel_id')">
|
||||
<p><a t-attf-href="/slides/course-#{request.params.get('channel_id')}">Return to the course.</a></p>
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="user_profile_content" inherit_id="website_profile.user_profile_content">
|
||||
<xpath expr="//div[@id='profile_about_badge']" position="before">
|
||||
<t t-if="channel">
|
||||
<div class="mb32">
|
||||
<h4><i class="fa fa-graduation-cap"/> Courses</h4>
|
||||
<t t-call="website_slides.display_course"/>
|
||||
</div>
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="display_course" name="Profile Courses">
|
||||
<t t-if="courses">
|
||||
<div class="row">
|
||||
<div class="col-lg-6 o_wprofile_slides_card" t-foreach="courses" t-as="course">
|
||||
<div class="card-body o_wprofile_slides_course_card_body o_wprofile_bb_grey" t-attf-onclick="location.href='/slides/#{slug(course)}';">
|
||||
<div class="row">
|
||||
<div class="o_wprofile_slides_course_img">
|
||||
<img class="o_wprofile_img_small mb-3" t-attf-src="/web/image/slide.channel/#{course.id}/image_small"/>
|
||||
</div>
|
||||
<div class="col-10 o_wprofile_slides_title">
|
||||
<b t-field="course.name"/>
|
||||
</div>
|
||||
<div class="o_wprofile_slide_course_rating">
|
||||
<t t-call="website_rating.rating_widget_stars_static">
|
||||
<t t-set="rating_avg" t-value="course.rating_avg"/>
|
||||
<t t-set="rating_count" t-value="course.rating_count"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<b>No followed course yet!</b>
|
||||
</t>
|
||||
</template>
|
||||
</data></odoo>
|
||||
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from . import controllers
|
||||
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Forum on course',
|
||||
'category': 'Hidden',
|
||||
'version': '1.0',
|
||||
'summary': 'Allows to link forum on a course',
|
||||
'description': """A Slide channel can be linked to forum. Also, profiles from slide and forum are regrouped together""",
|
||||
'depends': ['website_slides', 'website_forum'],
|
||||
'data': [
|
||||
'views/slide_channel_views.xml'
|
||||
],
|
||||
'auto_install': True,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import main
|
||||
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.http import request
|
||||
from odoo.addons.website_slides.controllers.main import WebsiteSlides
|
||||
|
||||
|
||||
class WebsiteSlidesForum(WebsiteSlides):
|
||||
# Profile
|
||||
# ---------------------------------------------------
|
||||
def _prepare_user_profile_parameters(self, **post):
|
||||
post = super(WebsiteSlidesForum, self)._prepare_user_profile_parameters(**post)
|
||||
if post.get('channel_id'):
|
||||
channel = request.env['slide.channel'].browse(int(post.get('channel_id')))
|
||||
if channel.forum_id:
|
||||
post.update({
|
||||
'forum_id': channel.forum_id.id,
|
||||
'no_forum': False
|
||||
})
|
||||
else:
|
||||
post.update({'no_forum': True})
|
||||
return post
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import forum
|
||||
from . import slide_channel
|
||||
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class Forum(models.Model):
|
||||
_inherit = 'forum.forum'
|
||||
|
||||
slide_channel_id = fields.Many2one('slide.channel', 'Course')
|
||||
@@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class Channel(models.Model):
|
||||
_inherit = 'slide.channel'
|
||||
|
||||
forum_id = fields.Many2one('forum.forum', 'Course Forum')
|
||||
|
||||
_sql_constraints = [
|
||||
('forum_uniq', 'unique (forum_id)', "Only one forum per slide channel!"),
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="website_slides_forum_channel_inherit_view_form" model="ir.ui.view">
|
||||
<field name="name">website.slides_forum.view.form.inherit.slide.channel</field>
|
||||
<field name="model">slide.channel</field>
|
||||
<field name="inherit_id" ref="website_slides.view_slide_channel_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//notebook" position="inside">
|
||||
<page string="Forum">
|
||||
<group>
|
||||
<field name="forum_id"/>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user