19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:30:27 +01:00
parent d1963a3c3a
commit 2d3ee4855a
7430 changed files with 2687981 additions and 2965473 deletions

View file

@ -1,15 +1,16 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
import uuid
from odoo import api, fields, models
from odoo.addons.mail.tools.discuss import Store
from odoo.addons.rating.models import rating_data
from odoo.modules.module import get_resource_path
from odoo.tools.misc import file_open
class Rating(models.Model):
_name = "rating.rating"
class RatingRating(models.Model):
_name = 'rating.rating'
_description = "Rating"
_order = 'write_date desc, id desc'
_rec_name = 'res_name'
@ -40,7 +41,7 @@ class Rating(models.Model):
rated_partner_id = fields.Many2one('res.partner', string="Rated Operator")
rated_partner_name = fields.Char(related="rated_partner_id.name")
partner_id = fields.Many2one('res.partner', string='Customer')
rating = fields.Float(string="Rating Value", group_operator="avg", default=0)
rating = fields.Float(string="Rating Value", aggregator="avg", default=0)
rating_image = fields.Binary('Image', compute='_compute_rating_image')
rating_image_url = fields.Char('Image URL', compute='_compute_rating_image')
rating_text = fields.Selection(rating_data.RATING_TEXT, string='Rating', store=True, compute='_compute_rating_text', readonly=True)
@ -51,16 +52,24 @@ class Rating(models.Model):
is_internal = fields.Boolean('Visible Internally Only', readonly=False, related='message_id.is_internal', store=True)
access_token = fields.Char('Security Token', default=_default_access_token)
consumed = fields.Boolean(string="Filled Rating")
rated_on = fields.Datetime(string="Rated On")
_sql_constraints = [
('rating_range', 'check(rating >= 0 and rating <= 5)', 'Rating should be between 0 and 5'),
]
_rating_range = models.Constraint(
'check(rating >= 0 and rating <= 5)',
'Rating should be between 0 and 5',
)
_consumed_idx = models.Index('(res_model, res_id, write_date) WHERE consumed IS TRUE')
_parent_consumed_idx = models.Index('(parent_res_model, parent_res_id, write_date) WHERE consumed IS TRUE')
@api.depends('res_model', 'res_id')
def _compute_res_name(self):
for rating in self:
name = self.env[rating.res_model].sudo().browse(rating.res_id).name_get()
rating.res_name = name and name[0][1] or ('%s/%s') % (rating.res_model, rating.res_id)
if rating.res_model and rating.res_id:
name = self.env[rating.res_model].sudo().browse(rating.res_id).display_name
else:
name = False
rating.res_name = name or f'{rating.res_model}/{rating.res_id}'
@api.depends('res_model', 'res_id')
def _compute_resource_ref(self):
@ -83,8 +92,8 @@ class Rating(models.Model):
for rating in self:
name = False
if rating.parent_res_model and rating.parent_res_id:
name = self.env[rating.parent_res_model].sudo().browse(rating.parent_res_id).name_get()
name = name and name[0][1] or ('%s/%s') % (rating.parent_res_model, rating.parent_res_id)
name = self.env[rating.parent_res_model].sudo().browse(rating.parent_res_id).display_name
name = name or f'{rating.parent_res_model}/{rating.parent_res_id}'
rating.parent_res_name = name
def _get_rating_image_filename(self):
@ -96,34 +105,43 @@ class Rating(models.Model):
self.rating_image_url = False
self.rating_image = False
for rating in self:
image_path = f'rating/static/src/img/{rating._get_rating_image_filename()}'
rating.rating_image_url = f'/{image_path}'
try:
image_path = get_resource_path('rating', 'static/src/img', rating._get_rating_image_filename())
rating.rating_image_url = '/rating/static/src/img/%s' % rating._get_rating_image_filename()
rating.rating_image = base64.b64encode(open(image_path, 'rb').read()) if image_path else False
except (IOError, OSError):
pass
with file_open(image_path, 'rb', filter_ext=('.png',)) as f:
rating.rating_image = base64.b64encode(f.read())
except OSError:
rating.rating_image = False
@api.depends('rating')
def _compute_rating_text(self):
for rating in self:
rating.rating_text = rating_data._rating_to_text(rating.rating)
# ------------------------------------------------------------
# CRUD
# ------------------------------------------------------------
@api.model_create_multi
def create(self, vals_list):
for values in vals_list:
if values.get('res_model_id') and values.get('res_id'):
values.update(self._find_parent_data(values))
if 'rating' in values or 'feedback' in values:
values['rated_on'] = fields.Datetime.now()
return super().create(vals_list)
def write(self, values):
if values.get('res_model_id') and values.get('res_id'):
values.update(self._find_parent_data(values))
return super(Rating, self).write(values)
def write(self, vals):
if vals.get('res_model_id') and vals.get('res_id'):
vals.update(self._find_parent_data(vals))
if 'rating' in vals or 'feedback' in vals:
vals['rated_on'] = fields.Datetime.now()
return super().write(vals)
def unlink(self):
# OPW-2181568: Delete the chatter message too
self.env['mail.message'].search([('rating_ids', 'in', self.ids)]).unlink()
return super(Rating, self).unlink()
return super().unlink()
def _find_parent_data(self, values):
""" Determine the parent res_model/res_id, based on the values to create or write """
@ -141,6 +159,10 @@ class Rating(models.Model):
data['parent_res_id'] = parent_res_model.id
return data
# ------------------------------------------------------------
# ACTIONS
# ------------------------------------------------------------
def reset(self):
for record in self:
record.write({
@ -158,3 +180,33 @@ class Rating(models.Model):
'res_id': self.res_id,
'views': [[False, 'form']]
}
# ------------------------------------------------------------
# TOOLS
# ------------------------------------------------------------
def _classify_by_model(self):
""" To ease batch computation of various ratings related methods they
are classified by model. Ratings not linked to a valid record through
res_model / res_id are ignored.
:returns: for each model having at least one rating in self, have
a sub-dict containing
* ratings: ratings related to that model;
* record IDs: records linked to the ratings of that model, in same
order;
:rtype: dict
"""
data_by_model = {}
for rating in self.filtered(lambda act: act.res_model and act.res_id):
if rating.res_model not in data_by_model:
data_by_model[rating.res_model] = {
'ratings': self.env['rating.rating'],
'record_ids': [],
}
data_by_model[rating.res_model]['ratings'] += rating
data_by_model[rating.res_model]['record_ids'].append(rating.res_id)
return data_by_model
def _to_store_defaults(self, target):
return ["rating", "rating_image_url", "rating_text"]