diff --git a/addons/website_forum/__manifest__.py b/addons/website_forum/__manifest__.py index ef5b7afefae..4a9131205d9 100644 --- a/addons/website_forum/__manifest__.py +++ b/addons/website_forum/__manifest__.py @@ -14,7 +14,6 @@ Ask questions, get answers, no distractions 'depends': [ 'auth_signup', 'website_mail', - 'website_partner', 'website_profile', ], 'data': [ diff --git a/addons/website_forum/static/src/scss/website_forum.scss b/addons/website_forum/static/src/scss/website_forum.scss index 36b3c72e2ff..85aaa69317c 100644 --- a/addons/website_forum/static/src/scss/website_forum.scss +++ b/addons/website_forum/static/src/scss/website_forum.scss @@ -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 { diff --git a/addons/website_profile/__init__.py b/addons/website_profile/__init__.py index dc5e6b693d1..8c67f893b37 100644 --- a/addons/website_profile/__init__.py +++ b/addons/website_profile/__init__.py @@ -2,3 +2,4 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import models +from . import controllers diff --git a/addons/website_profile/__manifest__.py b/addons/website_profile/__manifest__.py index 1575856c320..1214d3f03e8 100644 --- a/addons/website_profile/__manifest__.py +++ b/addons/website_profile/__manifest__.py @@ -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, diff --git a/addons/website_profile/controllers/__init__.py b/addons/website_profile/controllers/__init__.py new file mode 100644 index 00000000000..5d4b25db9c0 --- /dev/null +++ b/addons/website_profile/controllers/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import main diff --git a/addons/website_profile/controllers/main.py b/addons/website_profile/controllers/main.py new file mode 100644 index 00000000000..3372f6954e9 --- /dev/null +++ b/addons/website_profile/controllers/main.py @@ -0,0 +1,136 @@ +# -*- 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/'], 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, + 'notifications': self._get_badge_granted_messages(), + '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 _get_badge_granted_messages(self): + badge_subtype = request.env.ref('gamification.mt_badge_granted', raise_if_not_found=False) + msg = request.env['mail.message'] + if badge_subtype: + msg = msg.search([('subtype_id', '=', badge_subtype.id), ('needaction', '=', True)]) + return msg + + 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) diff --git a/addons/website_profile/models/__init__.py b/addons/website_profile/models/__init__.py index 980f26c8bd4..c26e78d1255 100644 --- a/addons/website_profile/models/__init__.py +++ b/addons/website_profile/models/__init__.py @@ -2,3 +2,4 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import gamification_badge +from . import website diff --git a/addons/website_profile/models/website.py b/addons/website_profile/models/website.py new file mode 100644 index 00000000000..76a37eba3d9 --- /dev/null +++ b/addons/website_profile/models/website.py @@ -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) diff --git a/addons/website_profile/static/src/scss/website_profile.scss b/addons/website_profile/static/src/scss/website_profile.scss new file mode 100644 index 00000000000..c636bd40d92 --- /dev/null +++ b/addons/website_profile/static/src/scss/website_profile.scss @@ -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; +} diff --git a/addons/website_profile/views/website_profile.xml b/addons/website_profile/views/website_profile.xml new file mode 100644 index 00000000000..06562d0e697 --- /dev/null +++ b/addons/website_profile/views/website_profile.xml @@ -0,0 +1,379 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +