from dateutil.relativedelta import relativedelta from odoo import api, fields, models from odoo.exceptions import ValidationError DEFAULT_EXPIRY_DAYS = 30 DEFAULT_WARNING_DAYS_BEFORE_EXPIRY = 7 MAX_IMAGES = 3 class CommunityClassified(models.Model): _name = 'community.classified' _description = 'Classified Listing' _order = 'post_date desc' title = fields.Char(required=True) category = fields.Selection( [ ('for_sale', 'For Sale'), ('housing', 'Housing'), ('services', 'Services'), ('jobs', 'Jobs'), ('other', 'Other'), ], default='other', required=True, ) description = fields.Html() image_ids = fields.One2many('community.classified.image', 'classified_id') contact_method = fields.Selection( [('email', 'Email'), ('phone', 'Phone'), ('both', 'Both')], default='email', required=True, ) contact_email = fields.Char() contact_phone = fields.Char() poster_partner_id = fields.Many2one('res.partner', string='Posted By', readonly=True) post_date = fields.Datetime(default=fields.Datetime.now, readonly=True) expiry_date = fields.Date(readonly=True) state = fields.Selection( [ ('pending_review', 'Pending Review'), ('published', 'Published'), ('expired', 'Expired'), ('rejected', 'Rejected'), ], default='pending_review', required=True, ) admin_notes = fields.Text() view_count = fields.Integer(default=0, readonly=True) @api.constrains('image_ids') def _check_max_images(self): for record in self: if len(record.image_ids) > MAX_IMAGES: raise ValidationError(f"A classified may have at most {MAX_IMAGES} images.") @api.model_create_multi def create(self, vals_list): for vals in vals_list: if not vals.get('expiry_date'): vals['expiry_date'] = self._compute_default_expiry_date() records = super().create(vals_list) records._notify_moderators_new_submission() return records @api.model def _get_expiry_days(self): return int(self.env['ir.config_parameter'].sudo().get_param( 'community_classifieds.expiry_days', DEFAULT_EXPIRY_DAYS )) @api.model def _compute_default_expiry_date(self): today = fields.Date.context_today(self) return today + relativedelta(days=self._get_expiry_days()) def action_publish(self): for record in self: record.write({'state': 'published', 'expiry_date': record._compute_default_expiry_date()}) return True def action_reject(self): self.write({'state': 'rejected'}) return True def action_renew(self): for record in self: record.write({ 'state': 'published', 'expiry_date': record._compute_default_expiry_date(), }) return True def _increment_view_count(self): self.sudo().write({'view_count': self.view_count + 1}) def _notify_moderators_new_submission(self): template = self.env.ref('community_classifieds.mail_template_new_submission', raise_if_not_found=False) if not template: return moderators = self.env.ref('community_classifieds.group_classifieds_moderator').user_ids for record in self: for moderator in moderators: if moderator.partner_id: template.send_mail(record.id, force_send=False, email_values={ 'recipient_ids': [(4, moderator.partner_id.id)], }) @api.model def _cron_expire_listings(self): today = fields.Date.context_today(self) expired = self.search([('state', '=', 'published'), ('expiry_date', '<', today)]) expired.write({'state': 'expired'}) return True @api.model def _cron_send_expiry_warnings(self): today = fields.Date.context_today(self) warning_date = today + relativedelta(days=DEFAULT_WARNING_DAYS_BEFORE_EXPIRY) soon_to_expire = self.search([('state', '=', 'published'), ('expiry_date', '=', warning_date)]) template = self.env.ref('community_classifieds.mail_template_expiry_warning', raise_if_not_found=False) if template: for record in soon_to_expire: template.send_mail(record.id, force_send=False) return True class CommunityClassifiedImage(models.Model): _name = 'community.classified.image' _description = 'Classified Listing Image' _order = 'sequence, id' classified_id = fields.Many2one('community.classified', required=True, ondelete='cascade') sequence = fields.Integer(default=10) image = fields.Binary(required=True, attachment=True)