diff --git a/addons/rating/__init__.py b/addons/rating/__init__.py new file mode 100644 index 00000000000..6528f226e57 --- /dev/null +++ b/addons/rating/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Odoo, Open Source Management Solution +# Copyright (C) 2014-TODAY Odoo SA (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +import models +import controllers diff --git a/addons/rating/__openerp__.py b/addons/rating/__openerp__.py new file mode 100644 index 00000000000..2ef3b8656aa --- /dev/null +++ b/addons/rating/__openerp__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +{ + 'name': 'Customer Rating', + 'version': '1.0', + 'category': 'Tools', + 'description': """ +This module allows a customer to give rating. +""", + 'author': 'Odoo SA', + 'website': 'http://odoo.com', + 'depends': [ + 'mail', + ], + 'data': [ + 'views/rating_view.xml', + 'views/rating_template.xml', + 'security/ir.model.access.csv' + ], + 'installable': True, + 'auto_install': False, +} diff --git a/addons/rating/controllers/__init__.py b/addons/rating/controllers/__init__.py new file mode 100644 index 00000000000..a84d81a791b --- /dev/null +++ b/addons/rating/controllers/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +import main diff --git a/addons/rating/controllers/main.py b/addons/rating/controllers/main.py new file mode 100644 index 00000000000..f2ab1ddc2db --- /dev/null +++ b/addons/rating/controllers/main.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- + +import werkzeug +from openerp import http +from openerp.http import request + + +class Rating(http.Controller): + + @http.route('/rating//', type='http', auth="public") + def add_rating(self, token, rate, **kwargs): + Rating = request.env['rating.rating'] + rating = Rating.search([('access_token', '=', token)]) + if rating: + is_rated = bool(rating.rating != -1) + if not is_rated: + request.env['rating.rating'].sudo().apply_rating(rate, token=token) + # redirect to the form view if logged person + if request.session.uid and not is_rated: + record = request.env[rating.res_model].browse(rating.res_id) + return werkzeug.utils.redirect('/web#model=%s&id=%s&view_type=form' % (record._name, record.id)) + return request.render('rating.rating_external_page_view', {'rating': rate, 'is_rated': is_rated}) + return request.not_found() diff --git a/addons/rating/models/__init__.py b/addons/rating/models/__init__.py new file mode 100644 index 00000000000..dacaf2fffbe --- /dev/null +++ b/addons/rating/models/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +import rating diff --git a/addons/rating/models/rating.py b/addons/rating/models/rating.py new file mode 100644 index 00000000000..401df694a1c --- /dev/null +++ b/addons/rating/models/rating.py @@ -0,0 +1,151 @@ +# -*- coding: utf-8 -*- +import uuid +from openerp import api, fields, models, _ + +class Rating(models.Model): + + _name = "rating.rating" + _description = "Rating" + _order = 'create_date desc' + _rec_name = 'res_name' + _sql_constraints = [ + ('rating_range', 'check(rating >= -1 and rating <= 10)', 'Rating should be between -1 to 10'), + ] + + @api.multi + @api.depends('res_model', 'res_id') + def _compute_res_name(self): + for record in self: + name = self.env[record.res_model].sudo().browse(record.res_id).name_get() + record.res_name = name and name[0][1] or ('%s/%s') % (record.res_model, record.res_id) + + @api.model + def new_access_token(self): + return uuid.uuid4().hex + + res_name = fields.Char(string='Resource Name', compute='_compute_res_name', store=True, help="The name of the rated ressource.") + res_model = fields.Char(string='Document Model', required=True, help="Model name of the rated object", index=True) + res_id = fields.Integer(string='Document ID', required=True, help="Identifier of the rated object", index=True) + rated_partner_id = fields.Many2one('res.partner', string="Rated Partner", help="Owner of the rated ressource") + partner_id = fields.Many2one('res.partner', string='Customer', help="Author of the rating") + rating = fields.Float(string="Rating", group_operator="avg", default=-1, help="Rating value") + 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") + + @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 + object, a message will be posted in the chatter. + :param float rating : the rating value to apply + :param list res_id : id of the rated object. + :param string res_model : name of model. + :param string token : access token + :return the rating record + """ + domain = [('access_token', '=', token)] if token else [('res_model', '=', res_model), ('res_id', '=', res_id)] + rating = self.search(domain, limit=1) + if rating: + rating.write({'rating' : rate}) + if hasattr(self.env[rating.res_model], 'message_post'): + record = self.env[rating.res_model].sudo().browse(rating.res_id) + record.message_post( + body="%s %s
" + % (rating.sudo().partner_id.name, _('rated it'), rate), + author_id=rating.partner_id and rating.partner_id.id or None # None will set the default author in mail_thread.py + ) + return rating + + @api.multi + def reset(self): + for record in self: + record.write({ + 'rating': -1, + 'access_token': record.new_access_token(), + 'feedback' : False + }) + + +class RatingMixin(models.AbstractModel): + + _name = 'rating.mixin' + _description = "Rating Mixin" + + rating_ids = fields.One2many('rating.rating', 'res_id', string='Rating', domain=lambda self: [('res_model', '=', self._name)]) + + @api.multi + def rating_send_request(self, template, partner_id, rated_partner_id, reuse_rating=True): + """ This method create (empty) rating objects for the current recordsets + and send this request by mail (given email template) with the given + rated_partner_id and partner_id as recipient and sender of the email. + :param template : the email template to send. The res_model of the + template must be 'rating.rating'. + :type template : mail.template + :param res_model : model name of the object to rated_partner_id + :type res_model : string + :param res_id : id the record to rate + :type res_id : int + :param partner_id : the recipient partner + :type partner : res.partner + :param rated_partner_id : the sender partner + :type rated_partner_id : res.partner + :param reuse_rating : if True, the rating of the current objects will + be reset. Otherwise a new rating will be create + :type reuse_rating : boolean + """ + if not rated_partner_id.email or not partner_id.email: + return False + Rating = self.env['rating.rating'] + res_model = self._name + 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 + } + if reuse_rating: + # search the existing rating for the given res_model/res_id + rating = Rating.search([('res_id', '=', res_id), ('res_model', '=', res_model)], limit=1) + if rating: # reset the rating + rating.reset() + else: # create a new one + rating = Rating.create(values) + else: + rating = Rating.create(values) + # send the mail + template.send_mail(rating.id, force_send=True) + + @api.multi + def rating_get_stats(self): + """ get the repatition of rating grade for the given res_ids. + :param list res_ids : optional list of ids. If not given, this will take the ids of the current recordset. + :param string res_model : optional name of model. If not given, the one of the current recordset will be used. + :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 + """ + data = self.env['rating.rating'].read_group([('res_model', '=', self._name), ('res_id', 'in', self.ids), ('rating', '>=', 0)], ['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 + + @api.multi + def rating_get_grades(self): + """ get the repatition of rating grade for the given res_ids. + :param list res_ids : optional list of ids. If not given, this will take the ids of the current recordset. + :param string res_model : optional name of model. If not given, the one of the current recordset will be used. + :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() + res = dict.fromkeys(['great', 'okay', 'bad'], 0) + for key in data: + if key >= 7: + res['great'] += data[key] + elif key > 3: + res['okay'] += data[key] + else: + res['bad'] += data[key] + return res diff --git a/addons/rating/security/ir.model.access.csv b/addons/rating/security/ir.model.access.csv new file mode 100644 index 00000000000..7d09058e2a7 --- /dev/null +++ b/addons/rating/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_rating_user,rating.rating.user,rating.model_rating_rating,base.group_user,1,1,1,0 +access_rating_public,rating.rating.public,rating.model_rating_rating,base.group_public,1,1,1,0 +access_rating_portal,rating.rating.portal,rating.model_rating_rating,base.group_portal,1,1,1,0 diff --git a/addons/rating/static/src/img/rating_-1.png b/addons/rating/static/src/img/rating_-1.png new file mode 100644 index 00000000000..8c32043915d Binary files /dev/null and b/addons/rating/static/src/img/rating_-1.png differ diff --git a/addons/rating/static/src/img/rating_0.png b/addons/rating/static/src/img/rating_0.png new file mode 100644 index 00000000000..3afa74fa3d8 Binary files /dev/null and b/addons/rating/static/src/img/rating_0.png differ diff --git a/addons/rating/static/src/img/rating_10.png b/addons/rating/static/src/img/rating_10.png new file mode 100644 index 00000000000..876fde227a1 Binary files /dev/null and b/addons/rating/static/src/img/rating_10.png differ diff --git a/addons/rating/static/src/img/rating_5.png b/addons/rating/static/src/img/rating_5.png new file mode 100644 index 00000000000..982b936e5e5 Binary files /dev/null and b/addons/rating/static/src/img/rating_5.png differ diff --git a/addons/rating/views/rating_template.xml b/addons/rating/views/rating_template.xml new file mode 100644 index 00000000000..773c1565e0d --- /dev/null +++ b/addons/rating/views/rating_template.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/addons/rating/views/rating_view.xml b/addons/rating/views/rating_view.xml new file mode 100644 index 00000000000..ee33139c872 --- /dev/null +++ b/addons/rating/views/rating_view.xml @@ -0,0 +1,81 @@ + + + + + rating.rating.kanban + rating.rating + + + + + + + + + + +
+
+ +
+
+
+
+
+
+
+ + + rating.rating.graph + rating.rating + + + + + + + + + + rating.rating.search + rating.rating + + + + + + + + + + + + + + + + + + + + + + + + + Rating + rating.rating + kanban,graph + +

+ There is no rating for this object at the moment. +

+
+
+
+