From b11e24c72666b19c3a5cfb95a5d0bf2c60d60344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Maes?= Date: Thu, 8 Jan 2015 11:33:08 +0100 Subject: [PATCH] [ADD] rating : base rating module contains a mixin and the meta model rating with graph and kanban views --- addons/rating/__init__.py | 22 +++ addons/rating/__openerp__.py | 21 +++ addons/rating/controllers/__init__.py | 2 + addons/rating/controllers/main.py | 23 ++++ addons/rating/models/__init__.py | 2 + addons/rating/models/rating.py | 151 +++++++++++++++++++++ addons/rating/security/ir.model.access.csv | 4 + addons/rating/static/src/img/rating_-1.png | Bin 0 -> 2495 bytes addons/rating/static/src/img/rating_0.png | Bin 0 -> 5184 bytes addons/rating/static/src/img/rating_10.png | Bin 0 -> 5116 bytes addons/rating/static/src/img/rating_5.png | Bin 0 -> 3513 bytes addons/rating/views/rating_template.xml | 20 +++ addons/rating/views/rating_view.xml | 81 +++++++++++ 13 files changed, 326 insertions(+) create mode 100644 addons/rating/__init__.py create mode 100644 addons/rating/__openerp__.py create mode 100644 addons/rating/controllers/__init__.py create mode 100644 addons/rating/controllers/main.py create mode 100644 addons/rating/models/__init__.py create mode 100644 addons/rating/models/rating.py create mode 100644 addons/rating/security/ir.model.access.csv create mode 100644 addons/rating/static/src/img/rating_-1.png create mode 100644 addons/rating/static/src/img/rating_0.png create mode 100644 addons/rating/static/src/img/rating_10.png create mode 100644 addons/rating/static/src/img/rating_5.png create mode 100644 addons/rating/views/rating_template.xml create mode 100644 addons/rating/views/rating_view.xml 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 0000000000000000000000000000000000000000..8c32043915da457e76b15db5702fed781e89b64f GIT binary patch literal 2495 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

~Uz@y9Qn~cIbEnCC6NW5L< zQpZWAjTao)n|t~?yoRMIbhtDrwWplSuldC{uTO?J;32q;4w-b_V!MRl>JJSCwP&{y zIp}%T`f_j57tjpF8{=esSEYP|%^#Pb&z*hVzu?Fte6|ymW;Uz2!%~@~l76}spYM}< z+rjs3h2CVt+|A>6?03HnSW~0AuyYhGrt z`*m8=wrnt{G4E_;th8WfQ?pO`rx6<1wvx}=@4WKCUd_cemtXbVA*zIotErpsxB8j0 zf`2%(#x&EM5{F|-?Ug3WY+To7a|cDlVmiI1qvic}_W+K=5#NdF9<0*Csi%~Shj473 z-)HA65~U!w$&#-5bB};+d~ZAudcs{d<|ph^@h(Q~-alEd)XpByy1JMFy?T&A93LDg zDRD3>UVkTCHOj8lw1jr34b9-=j<|QZI<%%I)TqU>BQ{`cT-0uKN$t$r2f~+ps-!@%Z{b(=4XnvL4G_UpjFir!rrtxHHqu5Ga{e~#-CBXK45xM^4YOHa87pH@{jvYi%*RGSNR>lf2vd8vGO znx4AmR$@zIHB7a5U7d_g8Wwpd>K*HH>cpPtzG=?!7ELzgx+;u+oK5Ie(f*w99Bg7b zvOQ^vgF?#wGP$c?f%)Y`_L)9OKFqy-$P6g&I>e~R= zX5Bd942mYPcU4nW8D4UeGq_*c=5zU$T^+11+sb-iOope|{cW+GoX-`dZWkH&BKS=}5KX9{_TZ}p` zYPHC=P=}%OS~U6AYze#6Y#f=)v!h&xWjZI};M^-tC^;fD_NDi?rjPnG2ND zIZ<%_9!4*7MTzGRLc&!!SG`oZE_H3(MpmSWe=Y^+9b)hd|rRMA8 zr;e2aPSkGobV3GZ!01^=UsZhZ^c^l)yL(Qa41l-EEB4ES@2zkb?(e4dN}~o`bFL6< za@86{rF;0~1=E_HOvn9HC0q08qcJ*EQsnxxQp9E76oN!m*(6-tnfb<@Y@IWwvbBQp zK1A@grbmDPp~9OwMrs>lSH~!&Z-LVVZ_sK^qYV%4CH&(1veMKG^wwpC=Ka*q(2mIB i%Szp8n%K(&FaQGU-n{DsE|vfOABQ%uKt0xTi~b*)0RJQa literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..982b936e5e5334522f8cfb139dfa1f03e5e1441f GIT binary patch literal 3513 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 + + + + + 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. +

+
+
+ +