summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorfda-odoo <fda@odoo.com>2017-12-06 09:57:34 +0100
committerfda-odoo <fda@odoo.com>2017-12-06 16:02:29 +0100
commit411afe54bed23e783070c7c5d5390d612887a629 (patch)
tree0edd21d0f030b2b1b01a2c7ad5b4cd8c89695216 /addons
parentebc4704b21b68f68175d924148f722341aef01fe (diff)
[IMP] website_rating_project: redesign project rating page
This commit improves and cleans the project rating page; - statistics of top 5 rated in last 15 days in right column of the page - move progress bar for tasks on right side of the page - show the detail of a rating when mouse hovering on the smiley Since the page is refreshed, the statistics computation is done with a single SQL request to extract all raw data to calculate period and partner stats to display. Thanks to @ror-odoo for the first implementation and to @jem-odoo for its review
Diffstat (limited to 'addons')
-rw-r--r--addons/website_rating_project/controllers/main.py92
-rw-r--r--addons/website_rating_project/static/src/css/website_rating_project.css3
-rw-r--r--addons/website_rating_project/static/src/js/website_rating_project.js27
-rw-r--r--addons/website_rating_project/static/src/less/website_rating_project.less34
-rw-r--r--addons/website_rating_project/views/website_rating_project.xml162
5 files changed, 252 insertions, 66 deletions
diff --git a/addons/website_rating_project/controllers/main.py b/addons/website_rating_project/controllers/main.py
index 4d6b3ecb65e..98c6cacc057 100644
--- a/addons/website_rating_project/controllers/main.py
+++ b/addons/website_rating_project/controllers/main.py
@@ -3,8 +3,6 @@
from werkzeug.exceptions import NotFound
-import datetime
-
from odoo import http
from odoo.http import request
@@ -17,34 +15,78 @@ class WebsiteRatingProject(http.Controller):
values = {'projects': projects}
return request.render('website_rating_project.index', values)
+ def _calculate_period_partner_stats(self, project_id):
+ # get raw data: number of rating by rated partner, by rating value, by period
+ request.env.cr.execute("""
+ SELECT
+ rated_partner_id,
+ rating,
+ COUNT(rating) as rating_count,
+ CASE
+ WHEN now()::date - create_date::date BETWEEN 0 AND 6 Then 'days_06'
+ WHEN now()::date - create_date::date BETWEEN 0 AND 15 Then 'days_15'
+ WHEN now()::date - create_date::date BETWEEN 0 AND 30 Then 'days_30'
+ WHEN now()::date - create_date::date BETWEEN 0 AND 90 Then 'days_90'
+ END AS period
+ FROM
+ rating_rating
+ WHERE
+ parent_res_model = 'project.project'
+ AND parent_res_id = %s
+ AND res_model = 'project.task'
+ AND rated_partner_id IS NOT NULL
+ AND create_date >= current_date - interval '90' day
+ AND rating IN (1,5,10)
+ GROUP BY
+ rated_partner_id, rating, period
+ """ % (project_id))
+
+ raw_data = request.env.cr.dictfetchall()
+
+ # periodical statistics
+ default_period_dict = {'rating_10': 0, 'rating_5': 0, 'rating_1': 0, 'total': 0}
+ period_statistics = {
+ 'days_06': dict(default_period_dict),
+ 'days_15': dict(default_period_dict),
+ 'days_30': dict(default_period_dict),
+ 'days_90': dict(default_period_dict),
+ }
+ for period_statistics_key in period_statistics.keys():
+ for row in raw_data:
+ if row['period'] <= period_statistics_key:
+ period_statistics[period_statistics_key]['rating_%s' % (int(row['rating']),)] += row['rating_count']
+ period_statistics[period_statistics_key]['total'] += row['rating_count']
+
+ # partner statistics
+ default_partner_dict = {'rating_10': 0, 'rating_5': 0, 'rating_1': 0, 'total': 0, 'rated_partner': None, 'percentage_happy': 0.0}
+ partner_statistics = {}
+ for row in raw_data:
+ if row['period'] <= 'days_15':
+ if row['rated_partner_id'] not in partner_statistics:
+ partner_statistics[row['rated_partner_id']] = dict(default_partner_dict)
+ partner_statistics[row['rated_partner_id']]['rated_partner'] = request.env['res.partner'].sudo().browse(row['rated_partner_id'])
+ partner_statistics[row['rated_partner_id']]['rating_%s' % (int(row['rating']),)] += row['rating_count']
+ partner_statistics[row['rated_partner_id']]['total'] += row['rating_count']
+
+ for partner_id, stat_values in partner_statistics.items():
+ stat_values['percentage_happy'] = (stat_values['rating_10'] / float(stat_values['total'])) * 100 if stat_values['total'] else 0.0
+
+ return {
+ 'partner_statistics': partner_statistics,
+ 'period_statistics': period_statistics,
+ }
+
@http.route(['/project/rating/<int:project_id>'], type='http', auth="public", website=True)
def page(self, project_id=None, **kw):
user = request.env.user
project = request.env['project.project'].sudo().browse(project_id)
# to avoid giving any access rights on projects to the public user, let's use sudo
# and check if the user should be able to view the project (project managers only if it's unpublished or has no rating)
- if not ((project.rating_status!='no') and project.portal_show_rating) and not user.sudo(user).has_group('project.group_project_manager'):
+ if not ((project.rating_status != 'no') and project.portal_show_rating) and not user.sudo(user).has_group('project.group_project_manager'):
raise NotFound()
- values = {
+
+ return request.render('website_rating_project.project_rating_page', {
'project': project,
- 'task_data': self._calculate_rating(project.id),
- }
- return request.render('website_rating_project.project_rating_page', values)
-
- def _calculate_rating(self, project_id):
- # Calculate rating for Tasks
- records = request.env["project.task"].sudo().search([('project_id', '=', project_id)])
- domain = [('res_model', '=', "project.task"), ('res_id', 'in', records.ids), ('consumed', '=', True)]
- ratings = request.env['rating.rating'].search(domain, order="id desc", limit=100)
-
- yesterday = (datetime.date.today() - datetime.timedelta(days=-1)).strftime('%Y-%m-%d 23:59:59')
- stats = {}
- for x in (7, 30, 90):
- todate = (datetime.date.today() - datetime.timedelta(days=x)).strftime('%Y-%m-%d 00:00:00')
- domdate = domain + [('create_date', '<=', yesterday), ('create_date', '>=', todate)]
- stats[x] = {1: 0, 5: 0, 10: 0}
- rating_stats = request.env['rating.rating'].read_group(domdate, [], ['rating'])
- total = sum(st['rating_count'] for st in rating_stats)
- for rate in rating_stats:
- stats[x][rate['rating']] = float("%.2f" % (rate['rating_count'] * 100.0 / total))
- return {'ratings': ratings, 'stats': stats}
+ 'ratings': request.env['rating.rating'].sudo().search([('consumed', '=', True), ('parent_res_model', '=', 'project.project'), ('parent_res_id', '=', project_id)], order='write_date DESC', limit=50),
+ 'statistics': self._calculate_period_partner_stats(project_id),
+ })
diff --git a/addons/website_rating_project/static/src/css/website_rating_project.css b/addons/website_rating_project/static/src/css/website_rating_project.css
deleted file mode 100644
index 6f5c549cc9a..00000000000
--- a/addons/website_rating_project/static/src/css/website_rating_project.css
+++ /dev/null
@@ -1,3 +0,0 @@
-.oe_website_rating_project .thumbnail{
- height: 355px;
-}
diff --git a/addons/website_rating_project/static/src/js/website_rating_project.js b/addons/website_rating_project/static/src/js/website_rating_project.js
new file mode 100644
index 00000000000..44c91980ae7
--- /dev/null
+++ b/addons/website_rating_project/static/src/js/website_rating_project.js
@@ -0,0 +1,27 @@
+odoo.define('website_rating_project.rating', function (require) {
+'use strict';
+
+ var time = require('web.time');
+ require('web.dom_ready');
+
+ if(!$('.o_website_rating_project').length) {
+ return $.Deferred().reject("DOM doesn't contain '.o_website_rating_project'");
+ }
+
+ /**
+ * Rating popover with some informations
+ */
+ $('.o_website_rating_project .o_rating_image').popover({
+ placement: 'bottom',
+ trigger: 'hover',
+ html: 'true',
+ content: function () {
+ var id = $(this).data('id');
+ var rating_date = $(this).data('rating-date');
+ var base_date = time.auto_str_to_date(rating_date);
+ var duration = moment(base_date).fromNow();
+ $("#rating_"+ id).find(".rating_timeduration").text(duration);
+ return $("#rating_"+ id).html();
+ }
+ });
+});
diff --git a/addons/website_rating_project/static/src/less/website_rating_project.less b/addons/website_rating_project/static/src/less/website_rating_project.less
new file mode 100644
index 00000000000..e95b1dbfac7
--- /dev/null
+++ b/addons/website_rating_project/static/src/less/website_rating_project.less
@@ -0,0 +1,34 @@
+.o_website_rating_project {
+ .thumbnail{
+ height: 240px;
+ }
+ .o_top_partner_rating_image {
+ height: 15px;
+ }
+ .o_top_partner_image {
+ height: 30px;
+ width: 30px;
+ }
+ .o_top_partner_feedback{
+ word-wrap: break-word;
+ }
+ .o_vertical_separator {
+ border-left: 1px solid #eeeeee
+ }
+ .o_rating_progress {
+ margin-bottom: 10px;
+ }
+ .o_rating_count {
+ display: inline-block;
+ min-width: 22px
+ }
+ .o_smiley_no_padding_left {
+ padding-left: 0;
+ }
+ .o_smiley_no_padding_right {
+ padding-right: 0;
+ }
+ .o_lighter_smileys {
+ opacity: 0.4
+ }
+} \ No newline at end of file
diff --git a/addons/website_rating_project/views/website_rating_project.xml b/addons/website_rating_project/views/website_rating_project.xml
index 74f5f48fb91..c3376dfe51f 100644
--- a/addons/website_rating_project/views/website_rating_project.xml
+++ b/addons/website_rating_project/views/website_rating_project.xml
@@ -4,7 +4,8 @@
<!-- CSS Style Bundle -->
<template id="assets_frontend" inherit_id="website.assets_frontend" name="Website Rating Project Assets">
<xpath expr="." position="inside">
- <link rel="stylesheet" href="/website_rating_project/static/src/css/website_rating_project.css"/>
+ <link rel="stylesheet" href="/website_rating_project/static/src/less/website_rating_project.less"/>
+ <script type="text/javascript" src="/website_rating_project/static/src/js/website_rating_project.js"/>
</xpath>
</template>
@@ -21,12 +22,17 @@
<t t-set="additional_title">Project Satisfaction</t>
<div id="wrap">
<div class="oe_structure"/>
- <div class="container oe_website_rating_project">
+ <div class="container o_website_rating_project">
<div class="row text-center">
<h2>Projects</h2>
</div>
<div class="oe_structure"/>
<div class="row">
+ <t t-if="not projects">
+ <div class="mt64 text-center">
+ No project rating puslished for now.
+ </div>
+ </t>
<t t-foreach="projects" t-as="project">
<div t-if="project.percentage_satisfaction_project != -1" class="col-sm-6 col-xs-4 col-md-4 col-lg-4">
<div class="thumbnail">
@@ -65,60 +71,140 @@
</t>
</template>
- <!-- Page : Rating of a particular project -->
- <template id="project_rating_data" name="Project Rating Page">
- <div class="progress">
- <div class="progress-bar progress-bar-success" t-attf-style="width: #{stats[duration][10]}%;" title="Happy">
- <t t-esc="int(stats[duration][10])"/>%
+ <!-- Page : Project Rating Page (and widgets) -->
+ <template id="portal_project_rating_progressbar">
+ <div class="progress o_rating_progress">
+ <t t-set="percent_rating_10" t-value="(stats['rating_10'] / float(stats['total'])) * 100 if stats['total'] else 0.0"/>
+ <div class="progress-bar progress-bar-success" t-attf-style="width: #{percent_rating_10}%;" title="Happy">
+ <t t-esc="percent_rating_10"/>%
</div>
- <div class="progress-bar progress-bar-warning" t-attf-style="width: #{stats[duration][5]}%;" title="Average">
- <t t-esc="int(stats[duration][5])"/>%
+ <t t-set="percent_rating_5" t-value="(stats['rating_5'] / float(stats['total'])) * 100 if stats['total'] else 0.0"/>
+ <div class="progress-bar progress-bar-warning" t-attf-style="width: #{percent_rating_5}%;" title="Average">
+ <t t-esc="percent_rating_5"/>%
</div>
- <div class="progress-bar progress-bar-danger" t-attf-style="width: #{stats[duration][1]}%;" title="Bad">
- <t t-esc="int(stats[duration][1])"/>%
+ <t t-set="percent_rating_1" t-value="(stats['rating_1'] / float(stats['total'])) * 100 if stats['total'] else 0.0"/>
+ <div class="progress-bar progress-bar-danger" t-attf-style="width: #{percent_rating_1}%;" title="Bad">
+ <t t-esc="percent_rating_1"/>%
</div>
</div>
</template>
- <template id="project_data_satisfaction">
- <div class="row">
- <div class="col-sm-4">
- <h5>Last 7 days</h5>
- <t t-set="duration" t-value="7"/>
- <t t-set="stats" t-value="data['stats']"/>
- <t t-call="website_rating_project.project_rating_data"/>
+ <template id="portal_project_rating_partner_stat">
+ <t t-foreach="partner_stats.values()" t-as="partner_stat">
+ <div class="row mt8" t-if="partner_stat['total']" title="Top 5 partner ratings of last 15 days.">
+ <div class="col-sm-1">
+ <img t-if="partner_stat['rated_partner'].image" class="o_top_partner_image" t-attf-src="data:image/png;base64,#{partner_stat['rated_partner'].image}"/>
+ <img t-if="not partner_stat['rated_partner'].image" class="o_top_partner_image" src='/web/static/src/img/placeholder.png'/>
+ </div>
+ <div class="col-sm-4 o_smiley_no_padding_right">
+ <span class="ml8" t-esc="partner_stat['rated_partner'].name"/>
+ </div>
+ <div class="col-sm-4 o_smiley_no_padding_right">
+ <div class="col-sm-4 o_smiley_no_padding_right o_smiley_no_padding_left">
+ <span t-if="partner_stat['rating_10']">
+ <img title="happy" class="o_top_partner_rating_image" src='/rating/static/src/img/rating_10.png'/>
+ <span class="o_rating_count" t-esc="partner_stat['rating_10']"/>
+ </span>
+ <span t-if="not partner_stat['rating_10']">
+ <img title="happy" class="o_top_partner_rating_image o_lighter_smileys" src='/rating/static/src/img/rating_10.png'/>
+ <span class="o_rating_count o_lighter_smileys" t-esc="partner_stat['rating_10']"/>
+ </span>
+ </div>
+ <div class="col-sm-4 o_smiley_no_padding_right o_smiley_no_padding_left">
+ <span t-if="partner_stat['rating_5']">
+ <img title="happy" class="o_top_partner_rating_image" src='/rating/static/src/img/rating_5.png'/>
+ <span class="o_rating_count" t-esc="partner_stat['rating_5']"/>
+ </span>
+ <span t-if="not partner_stat['rating_5']">
+ <img title="happy" class="o_top_partner_rating_image o_lighter_smileys" src='/rating/static/src/img/rating_5.png'/>
+ <span class="o_rating_count o_lighter_smileys" t-esc="partner_stat['rating_5']"/>
+ </span>
+ </div>
+ <div class="col-sm-4 o_smiley_no_padding_right o_smiley_no_padding_left">
+ <span t-if="partner_stat['rating_1']">
+ <img title="happy" class="o_top_partner_rating_image" src='/rating/static/src/img/rating_1.png'/>
+ <span class="o_rating_count" t-esc="partner_stat['rating_1']"/>
+ </span>
+ <span t-if="not partner_stat['rating_1']">
+ <img title="happy" class="o_top_partner_rating_image o_lighter_smileys" src='/rating/static/src/img/rating_1.png'/>
+ <span class="o_rating_count o_lighter_smileys" t-esc="partner_stat['rating_1']"/>
+ </span>
+ </div>
+ </div>
+ <div class="col-sm-3 o_smiley_no_padding_left">
+ <strong class="pull-right"><t t-esc="partner_stat['percentage_happy']"/>%</strong>
+ </div>
</div>
- <div class="col-sm-4">
- <h5>Last 30 days</h5>
- <t t-set="duration" t-value="30"/>
- <t t-set="stats" t-value="data['stats']"/>
- <t t-call="website_rating_project.project_rating_data"/>
+ </t>
+ </template>
+
+ <template id="portal_project_rating_popover">
+ <div t-attf-id="rating_#{rating.id}" class="container hidden">
+ <div class="row">
+ <div class="col-md-2">
+ <img t-if="rating.partner_id.image" class="o_top_partner_image" t-attf-src="data:image/png;base64,#{rating.partner_id.image}"/>
+ <img t-if="not rating.partner_id.image" class="o_top_partner_image" src='/web/static/src/img/placeholder.png'/>
+ </div>
+ <div class="col-md-10">
+ <div class="mt4">
+ <strong><t t-esc="rating.rated_partner_id.name"/></strong> - <span class="rating_timeduration"><t t-esc="rating.create_date"/></span>
+ </div>
+ <strong>Rated by: </strong><t t-esc="rating.partner_id.name" />
+ </div>
</div>
- <div class="col-sm-4">
- <h5>Last 3 months</h5>
- <t t-set="duration" t-value="90"/>
- <t t-set="stats" t-value="data['stats']"/>
- <t t-call="website_rating_project.project_rating_data"/>
+ <div class="row o_top_partner_feedback" t-if="rating.feedback">
+ <div class="col-md-12">
+ <t t-esc="rating.feedback" />
+ </div>
</div>
-
</div>
- <h5 class="page-header">Latest Feedbacks</h5>
- <t t-foreach="data['ratings']" t-as="rating" class="media">
- <img t-attf-src='/rating/static/src/img/rating_#{int(rating.rating)}.png' t-att-alt="rating.res_name" t-att-title="rating.res_name"/>
- </t>
</template>
<template id="project_rating_page" name="Project Rating Page">
<t t-call="website.layout">
<div id="wrap">
<div class="oe_structure"/>
- <div class="container oe_website_rating_project">
+ <div class="container o_website_rating_project">
<h1 t-esc="project.name" class="text-center"/>
<div class="row mb32">
- <div class="col-md-8">
- <t t-set="type" t-value="'Tasks'"/>
- <t t-set="data" t-value="task_data"/>
- <t t-call="website_rating_project.project_data_satisfaction"/>
+ <div>
+ <h3 class="page-header">Customer Ratings</h3>
+ <div class="row">
+ <div class="col-md-7">
+ <t t-foreach="ratings" t-as="rating">
+ <img t-attf-src='/rating/static/src/img/rating_#{int(rating.rating)}.png'
+ class="mt4 o_rating_image"
+ t-att-data-id="rating.id"
+ t-att-data-rating-date="rating.write_date"/>
+ <t t-call="website_rating_project.portal_project_rating_popover"/>
+ </t>
+ </div>
+ <div class="col-md-5 o_vertical_separator">
+ <div t-if="statistics['period_statistics']['days_06']">
+ <h5>Last 7 days</h5>
+ <t t-call="website_rating_project.portal_project_rating_progressbar">
+ <t t-set="stats" t-value="statistics['period_statistics']['days_06']"/>
+ </t>
+ </div>
+ <div t-if="statistics['period_statistics']['days_30']">
+ <h5>Last 30 days</h5>
+ <t t-call="website_rating_project.portal_project_rating_progressbar">
+ <t t-set="stats" t-value="statistics['period_statistics']['days_30']"/>
+ </t>
+ </div>
+ <div t-if="statistics['period_statistics']['days_90']">
+ <h5>Last 3 months</h5>
+ <t t-call="website_rating_project.portal_project_rating_progressbar">
+ <t t-set="stats" t-value="statistics['period_statistics']['days_90']"/>
+ </t>
+ </div>
+ <div>
+ <t t-call="website_rating_project.portal_project_rating_partner_stat">
+ <t t-set="partner_stats" t-value="statistics['partner_statistics']"/>
+ </t>
+ </div>
+ </div>
+ </div>
</div>
</div>
</div>