diff --git a/addons/rating/models/rating.py b/addons/rating/models/rating.py
index 44d446b4b0c..7a21a2ea77d 100644
--- a/addons/rating/models/rating.py
+++ b/addons/rating/models/rating.py
@@ -31,6 +31,8 @@ class Rating(models.Model):
feedback = fields.Text('Feedback reason', help="Reason of the rating")
access_token = fields.Char(string='Security Token', default=new_access_token, help="Access token to set the rating of the value")
+ message_id = fields.Many2one('mail.message', string="Linked message", help="Associated message when posting a review. Mainly used in website addons.", index=True)
+
@api.model
def apply_rating(self, rate, res_model=None, res_id=None, token=None):
""" apply a rating for given res_model/res_id or token. If the res_model is a mail.thread
@@ -100,10 +102,10 @@ class RatingMixin(models.AbstractModel):
for record in self:
res_id = record.id
values = {
- 'res_model' : res_model,
- 'res_id' : res_id,
- 'partner_id' : partner_id.id,
- 'rated_partner_id' : rated_partner_id.id
+ 'res_model': res_model,
+ 'res_id': res_id,
+ 'partner_id': partner_id.id,
+ 'rated_partner_id': rated_partner_id.id
}
if reuse_rating:
# search the existing rating for the given res_model/res_id
@@ -118,25 +120,46 @@ class RatingMixin(models.AbstractModel):
template.send_mail(rating.id, force_send=True)
@api.multi
- def rating_get_stats(self):
+ def rating_get_repartition(self, add_stats=False, domain=None):
""" get the repatition of rating grade for the given res_ids.
- :return dictionnary where the key is the rating value (the note), and the value, the number of object (res_model, res_id) having the value
+ :param add_stats : flag to add stat to the result
+ :type add_stats : boolean
+ :param domain : optional extra domain of the rating to include/exclude in repartition
+ :return dictionnary
+ if not add_stats, the dict is like
+ - key is the rating value (integer)
+ - value is the number of object (res_model, res_id) having the value
+ otherwise, key is the value of the information (string) : either stat name (avg, total, ...) or 'repartition'
+ containing the same dict if add_stats was False.
"""
- data = self.env['rating.rating'].read_group([('res_model', '=', self._name), ('res_id', 'in', self.ids), ('rating', '>=', 0)], ['rating'], ['rating', 'res_id'])
+ base_domain = [('res_model', '=', self._name), ('res_id', 'in', self.ids), ('rating', '>=', 0)]
+ if domain:
+ base_domain += domain
+ data = self.env['rating.rating'].read_group(base_domain, ['rating'], ['rating', 'res_id'])
# init dict with all posible rate value, except -1 (no value for the rating)
- res = dict.fromkeys(range(11), 0)
- res.update((d['rating'], d['rating_count']) for d in data)
- return res
+ values = dict.fromkeys(range(11), 0)
+ values.update((d['rating'], d['rating_count']) for d in data)
+ # add other stats
+ if add_stats:
+ rating_number = sum(values.values())
+ result = {
+ 'repartition': values,
+ 'avg': sum([float(key*values[key]) for key in values])/rating_number if rating_number > 0 else 0,
+ 'total': reduce(lambda x, y: y['rating_count']+x, data, 0),
+ }
+ return result
+ return values
@api.multi
- def rating_get_grades(self):
+ def rating_get_grades(self, domain=None):
""" get the repatition of rating grade for the given res_ids.
+ :param domain : optional domain of the rating to include/exclude in grades computation
:return dictionnary where the key is the grade (great, okay, bad), and the value, the number of object (res_model, res_id) having the grade
the grade are compute as 0-30% : Bad
31-69%: Okay
70-100%: Great
"""
- data = self.rating_get_stats()
+ data = self.rating_get_repartition(domain=domain)
res = dict.fromkeys(['great', 'okay', 'bad'], 0)
for key in data:
if key >= 7:
@@ -146,3 +169,22 @@ class RatingMixin(models.AbstractModel):
else:
res['bad'] += data[key]
return res
+
+ @api.multi
+ def rating_get_stats(self, domain=None):
+ """ get the statistics of the rating repatition
+ :param domain : optional domain of the rating to include/exclude in statistic computation
+ :return dictionnary where
+ - key is the the name of the information (stat name)
+ - value is statistic value : 'percent' contains the repartition in percentage, 'avg' is the average rate
+ and 'total' is the number of rating
+ """
+ data = self.rating_get_repartition(domain=domain, add_stats=True)
+ result = {
+ 'avg': data['avg'],
+ 'total': data['total'],
+ 'percent': dict.fromkeys(range(11), 0),
+ }
+ for rate in data['repartition']:
+ result['percent'][rate] = (data['repartition'][rate] * 100) / data['total'] if data['total'] > 0 else 0
+ return result
diff --git a/addons/rating/static/src/css/rating_common.css b/addons/rating/static/src/css/rating_common.css
new file mode 100644
index 00000000000..ff84353a8eb
--- /dev/null
+++ b/addons/rating/static/src/css/rating_common.css
@@ -0,0 +1,48 @@
+/* Star Widget */
+.o_rating_star_card{
+ margin-bottom: 5px;
+}
+
+.o_rating_star_card .stars {
+ display: inline-block;
+ color: #FACC2E;
+ margin-right: 15px;
+}
+
+.o_rating_star_card .stars i {
+ margin-right: -3px;
+ text-align: center;
+}
+
+.o_rating_star_card .stars.enabled{
+ cursor: pointer;
+}
+
+.o_rating_star_card .rate_text{
+ display: inline-block;
+}
+
+
+/* Rating Card */
+.o_rating_card .progress {
+ position: relative;
+ height: 25px;
+ margin-bottom: 3px;
+}
+.o_rating_card .progress-bar {
+ background-color: #FACC2E;
+}
+.o_rating_card .progress > .progress_type {
+ position: absolute;
+ left: 0px;
+ font-weight: 800;
+ padding: 3px 10px 2px 10px;
+ color: rgb(120, 100, 100);
+ width: 100%;
+}
+.o_rating_card .progress > .progress_completed {
+ position: absolute;
+ right: 0px;
+ font-weight: 800;
+ padding: 3px 10px 2px;
+}
\ No newline at end of file
diff --git a/addons/rating/static/src/js/rating_common.js b/addons/rating/static/src/js/rating_common.js
new file mode 100644
index 00000000000..67994e72695
--- /dev/null
+++ b/addons/rating/static/src/js/rating_common.js
@@ -0,0 +1,127 @@
+odoo.define('rating.rating', function (require) {
+ "use strict";
+
+ var Widget = require('web.Widget');
+ var translation = require('web.translation');
+ var _t = translation._t;
+
+ // Star Widget
+ var RatingStarWidget = Widget.extend({
+ template: 'rating.rating_star_card',
+ events: {
+ "mousemove .stars i" : "moveOnStars",
+ "mouseleave .stars i" : "moveOut",
+ "click .stars" : "clickOnStar",
+ "mouseleave .stars" : "moveOutStars",
+ },
+ init: function(parent, options){
+ this._super.apply(this, arguments);
+ this.options = _.defaults(options || {}, {
+ 'rating_default_value': 0,
+ 'rating_disabled': 0,
+ });
+ this.labels = {
+ '0': "",
+ '1': _t("I hate it"),
+ '2': _t("I don't like it"),
+ '3': _t("It's okay"),
+ '4': _t("I like it"),
+ '5': _t("I love it"),
+ };
+ this.user_click = false; // user has click or not
+ this.set("star_value", 0);
+ this.on("change:star_value", this, this.changeStars);
+ },
+ start: function(){
+ this.$input = this.$('input');
+ this.star_list = this.$('.stars').find('i');
+ // set the default value
+ this.set("star_value", this.options.rating_default_value);
+ this.is_editable = !this.options.rating_disabled;
+ },
+ attachTo: function(el){
+ this._super.apply(this, arguments);
+ // set the default value and bind event
+ var default_value = this.$('input').data('default');
+ default_value = this.roundToHalf(default_value);
+ this.set('star_value', default_value);
+ // is_editable value from DOM
+ this.is_editable = !this.$input.data('is_disabled');
+ },
+ changeStars: function(){
+ var val = this.get("star_value");
+ var index = Math.floor(val);
+ var decimal = val - index;
+ // reset the stars
+ this.star_list.removeClass('fa-star fa-star-half-o').addClass('fa-star-o');
+
+ this.$('.stars').find("i:lt("+index+")").removeClass('fa-star-o fa-star-half-o').addClass('fa-star');
+ if(decimal){
+ this.$('.stars').find("i:eq("+(index)+")").removeClass('fa-star-o fa-star fa-star-half-o').addClass('fa-star-half-o');
+ }
+
+ this.$input.val(val);
+ this.$('.rate_text .label').text(this.labels[index]);
+ },
+ moveOut: function(){
+ if(!this.user_click && this.is_editable){
+ this.set("star_value", 0);
+ }
+ this.user_click = false;
+ },
+ moveOnStars: function(e){
+ if(this.is_editable){
+ this.$('.rate_text').show();
+ var index = this.$('.stars i').index(e.currentTarget);
+ this.set("star_value", index+1);
+ }
+ },
+ clickOnStar: function(e){
+ if(this.is_editable){
+ this.user_click = true;
+ }
+ },
+ moveOutStars: function(e){
+ this.$('.rate_text').hide();
+ },
+ roundToHalf: function(value) {
+ var converted = parseFloat(value); // Make sure we have a number
+ var decimal = (converted - parseInt(converted, 10));
+ decimal = Math.round(decimal * 10);
+ if(decimal == 5){
+ return (parseInt(converted, 10)+0.5);
+ }
+ if((decimal < 3) || (decimal > 7)){
+ return Math.round(converted);
+ }else{
+ return (parseInt(converted, 10)+0.5);
+ }
+ },
+ });
+
+ var page_widgets = {};
+
+ $(document).ready(function(){
+ // Rating Card
+ $('[data-toggle="rating-popover"]').popover({
+ html : true,
+ trigger: 'hover',
+ title: function() {
+ return $(this).parent().find('.rating_popover').find('.popover-title').html();
+ },
+ content: function() {
+ return $(this).parent().find('.rating_popover').find('.popover-content').html();
+ }
+ });
+ // Rating Star Widget instances
+ $('.o_rating_star_card').each(function(index, elem){
+ page_widgets[index] = new RatingStarWidget();
+ page_widgets[index].attachTo(elem);
+ })
+ });
+
+ return {
+ RatingStarWidget: RatingStarWidget,
+ rating_star_widgets: page_widgets,
+ };
+});
\ No newline at end of file
diff --git a/addons/rating/static/src/xml/rating_common.xml b/addons/rating/static/src/xml/rating_common.xml
new file mode 100644
index 00000000000..fb703785ae7
--- /dev/null
+++ b/addons/rating/static/src/xml/rating_common.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
diff --git a/addons/rating/views/rating_template.xml b/addons/rating/views/rating_template.xml
index 7ff1c3b80e2..8f84562265e 100644
--- a/addons/rating/views/rating_template.xml
+++ b/addons/rating/views/rating_template.xml
@@ -1,6 +1,15 @@
+
+
+
+
+
+
+
+
+
@@ -8,7 +17,84 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %
+
+
+
+
+
+
+ %
+
+
+
+
+
+
+
+
+
+
+
@@ -24,5 +110,6 @@
Sorry you have already voted !
+