# -*- coding: utf-8 -*- from odoo import api, fields, models from odoo.exceptions import ValidationError import logging _logger = logging.getLogger(__name__) class Rating(models.Model): _inherit = 'rating.rating' _description = 'Rating with 5-star support' # Enable audit logging for rating changes _log_access = True # Override rating field to support 0-5 range rating = fields.Float( string='Rating Value', required=True, help='Rating value: 0 (no rating), 1-5 (stars)', aggregator="avg", tracking=True # Track changes to rating value ) # Computed fields for star display rating_stars_filled = fields.Integer( compute='_compute_rating_stars', string='Filled Stars', help='Number of filled stars to display' ) rating_stars_empty = fields.Integer( compute='_compute_rating_stars', string='Empty Stars', help='Number of empty stars to display' ) # Audit fields - track who submitted/modified the rating feedback = fields.Text( string='Feedback', tracking=True # Track changes to feedback ) consumed = fields.Boolean( string='Rating Submitted', tracking=True # Track when rating is consumed ) @api.constrains('rating') def _check_rating_value(self): """Validate rating is between 0 and 5""" for record in self: if record.rating < 0 or record.rating > 5: raise ValidationError( 'Rating must be between 0 and 5 stars. ' 'Received value: %s' % record.rating ) # Allow 0 (no rating) or values between 1-5 if record.rating > 0 and record.rating < 1: raise ValidationError( 'Rating must be 0 (no rating) or between 1 and 5 stars. ' 'Received value: %s' % record.rating ) @api.depends('rating') def _compute_rating_stars(self): """Compute the number of filled and empty stars""" for record in self: # Round rating to nearest integer for display rating_int = round(record.rating) record.rating_stars_filled = rating_int record.rating_stars_empty = 5 - rating_int def _get_rating_stars_html(self): """Generate HTML for star display""" self.ensure_one() filled_stars = self.rating_stars_filled empty_stars = self.rating_stars_empty # Unicode star characters filled_star = '★' # U+2605 BLACK STAR empty_star = '☆' # U+2606 WHITE STAR # Generate HTML with stars html = '' html += '' + (filled_star * filled_stars) + '' html += '' + (empty_star * empty_stars) + '' html += '' return html def write(self, vals): """Override write to add audit logging for rating changes""" # Log rating changes for audit trail for record in self: if 'rating' in vals and vals['rating'] != record.rating: old_value = record.rating new_value = vals['rating'] _logger.info( 'Rating modified: ID=%s, Model=%s, ResID=%s, OldValue=%s, NewValue=%s, User=%s', record.id, record.res_model, record.res_id, old_value, new_value, self.env.user.login ) # Post message to chatter if available if record.res_model and record.res_id: try: resource = self.env[record.res_model].browse(record.res_id) if resource.exists() and hasattr(resource, 'message_post'): resource.message_post( body=f'Rating updated from {int(old_value)} to {int(new_value)} stars', subject='Rating Updated', message_type='notification', subtype_xmlid='mail.mt_note' ) except Exception as e: _logger.warning('Could not post rating change to chatter: %s', str(e)) return super(Rating, self).write(vals) @api.model_create_multi def create(self, vals_list): """Override create to add audit logging for new ratings""" records = super(Rating, self).create(vals_list) # Log new ratings for audit trail for record in records: if record.rating > 0: _logger.info( 'Rating created: ID=%s, Model=%s, ResID=%s, Value=%s, User=%s', record.id, record.res_model, record.res_id, record.rating, self.env.user.login ) return records